很抱歉,如果问题标题具有误导性;当我试图找到问题的根源时,它正在进行中。
我正在针对.Single( Row => ... ... )
的{{1}}扩展程序进行.AsEnumerable( )
调用,该DataTable
扩展程序会抛出异常。查看表格,存在两行是不可能的,因为查看SOURCE表(数据库中应该填充此DataTable
的那一行),我只看到其中一行。 / p>
为了缩小问题的范围,我正在为表分配一个主键,现在正在生成一个不可能的异常,告诉我表中已存在主键,这应该不是这种情况,因为表应该已经 创建。
为什么会这样?
我希望在形成这个问题的过程中,我会在其中一个问题上找到解决方案"与同事/纸板剪影 AHA &#谈话34;瞬间;没那么多。
这里有相当多的代码,我会尽可能地解释我能够完成的任务(我也道歉,因为我不知道究竟什么所有正确的术语是什么我想描述一下。)
我正在开发一个项目组件,它将有效地充当在线数据库和桌面应用程序之间的中介。由于可移植性是主要关注点,因此我将组件主要用于事件驱动。
还有很多事情必须同时进行;因此,我努力以异步方式实现此组件(async
和await
)。
该程序依赖于一个静态类(我已构建),其中包含许多DataTables
。
在我发布我的代码之前,我应该说我最初的怀疑是并行运行的多个异步任务是......"相互崩溃"不知何故。不过,我不知道是否确实如此。
我将从我认为问题根源的地方开始,然后逐步完成堆栈。为了简洁起见,我将尝试以深度优先的方式做到这一点。如有必要,可在以后添加更多内容。
在进入跟踪之前,我想发布我主要怀疑的方法:
/// <summary>
/// Asynchronously read tables for this SqlLibAsync instance.
/// </summary>
public async Task ReadTables( ) {
this.DS = new DataSet( );
using ( MySqlConnection MSQCon = new MySqlConnection( this.ConnectionString ) ) {
await MSQCon.OpenAsync( );
try {
foreach ( MySqlDataAdapter Adapter in this.Adapters ) {
Adapter.SelectCommand.Connection = MSQCon;
await Adapter.FillAsync( this.DS,
Adapter.TableMappings.Cast<DataTableMapping>()
.First( )
.SourceTable
); //In fact, could the troubles be originating from this .Cast<>() call?
}
} catch ( Exception ex ) {
ex.Report( );
}
await MSQCon.CloseAsync( );
}
if ( this.DS.Tables.Count == 0 )
await this.ReadTables( );
}
这种方法给我带来了很多麻烦,经历了几次迭代。在过去,我收到一个异常,通知我在DataReader
已经访问连接时发生了连接尝试,或者我认为是因为我正在使用此类的多个实例异步工作并且这种工作的方式不合适(基本上,从一台计算机到同一个数据库的多个连接),所以我选择了这条似乎处理过这个问题的路线,但现在我得到了这个新问题。我相信,出于某种原因,并行异步任务和这种方法并没有相处,但我不知道为什么。
在我遇到问题的情况下,使用此方法的树的开头就在这里:
public static async Task Start( ) {
/*I have omitted code that appears to be working properly. #1*/
bool
Pending = true;
/*Another Code Omission. #2*/
while ( Session.Instance.Status != SessionStatus.FINALIZED && Session.UserID != 0 ) {
List<Task> TList = new List<Task>();
TList.Add( Session.Instance.PlayerCheck( ) );
switch ( Session.Instance.Status ) {
case SessionStatus.PENDING:
if ( Pending ) {
Pending = false;
TList.Add( Session.OnSessionReport( ) );
}
break;
}
/*Another Omission #3*/
await Task.WhenAll( TList );
}
/*More omitted code*/
}
我们将从Session.Instance
private static Session _Instance; // Private static reference to a session.
/// <summary>
/// Static reference to instance of the Session.
/// </summary>
public static Session Instance { get { return Session._Instance; } }
接下来,我们拥有Status
类的Session
属性:
private SessionStatus Status { //Retrieve fresh session status.
get {
switch ( this.FreshRow.Field<string>( "status" ) ) {
/*Translates the incoming string into an enum. #4*/
}
}
}
Status
属性引用FreshRow属性:
private DataRow FreshRow { //Retrieves datarow from which fresh session information can be extracted. (Status, BuzzedInUser, etc).
get {
if ( this.SessionID == 0 )
throw new ObjectDisposedException( "Session", "Illegal attempt to access disposed Session object." );
return QatiTables.GameSessions.Fresh.AsEnumerable( ).Single( Row => Row.Field<UInt32>( "game_session_id" ).Equals( this.SessionID ) );
}
/*Session.SessionID is a simple property with a getter and a private setter.*/
}
从这里我们有QatiTables.GameSessions
属性:
public static SqlTableAsync GameSessions {
get {
return QatiTables.Tables[1] ?? ( QatiTables.Tables[1] = new SqlTableAsync(
"QAndATimeServer.txt",
"game_sessions",
"created_by = @param_val_0", //We have to grab all of these every time because the row status may change mid-game and we would lose it.
new object[ ] { QatiTables.Users.Fresh.AsEnumerable( ).Single( ).Field<UInt32>( "user_id" ) } ) );
}
/*The referenced QatiTables.Tables[] is an array of SqlTableAsync objects.*/
}
此属性使用SqlTableAsync
类:
/// <summary>
/// SELECT * FROM [TABLE] WHERE [FILTER]
/// </summary>
/// <param name="ServerFile">FTP File containing database connection data.</param>
/// <param name="TableName">Table to retrieve.</param>
/// <param name="Filter">Query Filter</param>
/// <param name="Parameters">Parameters on which to filter.</param>
public SqlTableAsync( string ServerFile, string TableName, string Filter, object[ ] Parameters ) {
this.TableLib = new SqlLibAsync( "QAndATimeServer.txt" );
try { this.TableLib.GetTable( TableName, Filter, Parameters ).Wait( ); } catch ( Exception e ) {
e.Report( );
/*Report is a custom extension method for exceptions. #5*/
}
this.TableName = TableName;
}
在这个构造函数中,我们有SqlLibAsync
类:
/// <summary>
/// Initialize a new SqlLib
/// </summary>
/// <param name="DatabaseConnection">Text File from which to read database connection parameters on File Server</param>
public SqlLibAsync( string DatabaseConnection ) { this.ConnectionFile = DatabaseConnection; }
/// <summary>
/// Set FTP file from which to read connection data.
/// </summary>
private string ConnectionFile {
set {
/*This populates local variables with information from a file read
directly from an FTP server. As such, I have omitted this code as
it functions as it should, and also as it contains some sensitive
bits of information.*/
}
}
我们已经到了第一家分店的尽头!万岁!
回到顶端,我们的下一个引用方法是SqlLibAsync.GetTable( )
方法:
/// <summary>
/// SELECT * FROM [TABLE] WHERE [FILTER]
/// </summary>
/// <param name="Table">Table Name</param>
/// <param name="Filter">Query Filter</param>
/// <param name="Parameters">Object of parameters with which to populate filter.</param>
public async Task GetTable( string Table, string Filter, object[ ] Parameters ) {
await this.GetTables( new string[ ] { Table }, new string[ ] { Filter }, new object[ ][ ] { Parameters } );
}
此方法引用SqlLibAsync.GetTables(...)方法:
/// <summary>
/// Return Multiple Tables from a Data Set.
/// </summary>
/// <param name="Tables">Tables for which to query</param>
public async Task GetTables( string[ ] Tables, string[ ] Filters, object[ ][ ] Parameters ) {
this.Adapters = new MySqlDataAdapter[Tables.Length];
int FilterIndex;
object[ ] ParameterSet = null;
string Query = null, Filter = null;
foreach ( string Table in Tables ) {
FilterIndex = Tables.ToList( ).IndexOf( Table );
Filter = Filters[FilterIndex];
ParameterSet = Parameters[FilterIndex];
Query = "SELECT * FROM " + Table + " WHERE ";
if ( string.IsNullOrEmpty( Filter ) )
Query += "1;";
else
Query += Filter + ";";
MySqlDataAdapter Adapter = new MySqlDataAdapter( new MySqlCommand( Query ) { CommandType = CommandType.Text } );
if ( ParameterSet != null )
for ( int x = 0; x < ParameterSet.Length; x++ )
Adapter.SelectCommand.Parameters.AddWithValue( "@param_val_" + x, ParameterSet[x] );
Adapter.TableMappings.Add( Table, Table );
this.Adapters[Tables.ToList( ).IndexOf( Table )] = Adapter;
}
await this.ReadTables( );
}
此方法使用第一个描述的SqlLibAsync.ReadTables
方法。
这将我们带到了第二个分支的末尾。
我们现在撤消对QatiTables.GameSessions
属性的QatiTables.Users
属性引用:
public static SqlTableAsync Users {
get {
return QatiTables.Tables[0] ?? ( QatiTables.Tables[0] = new SqlTableAsync(
"QAndATimeServer.txt",
"users",
"last_ip_address = @param_val_0 AND role IN (@param_val_1, @param_val_2) AND is_login = @param_val_3",
new object[ ] { Methods.IPAddress, "admin", "entertainer", 1 } ) );
}
}
此属性引用静态Methods.IPAddress
属性,我将省略该属性,因为我觉得使用它的上下文定义了它。
此属性还使用了前面描述的SqlTablesAsync
类及其方法和属性。
此属性还将我们带到另一个分支的末尾。
下一个分支是Fresh
类的SqlTableAsync
属性,在FreshRow
类的Session
属性中引用:
/// <summary>
/// Get fresh table.
/// </summary>
public DataTable Fresh { get {
try {
this.TableLib.ReadTables( ).Wait( );
return this.TableLib.Tables[this.TableName];
} catch ( Exception EX ) {
EX.Report( );
return null;
}
} }
这引用了TableLib
变量;本地SqlTableAsync
类对象。它还引用了该类的ReadTables
方法(如前所述),以及该类的Tables
属性:
/// <summary>
/// Get this SqlLibs table set.
/// </summary>
public DataTableCollection Tables { get { return this.DS.Tables; } }
此属性引用了类DS
变量,即先前方法中描述的DataSet
。
最后,我们已经到达了我们的第一个主要分支的末尾,Status
类的Session
属性,以及它所有相关的类,属性,方法和函数,它们将我们带到我们的下一个主要分支:Session类的UserID属性:
private static UInt32 UserID { // Static Logged In UserID
get {
IEnumerable<DataRow> Users = QatiTables.Users.Fresh.AsEnumerable( ); //To avoid multiple unnecessary queries.
return Users.Count( ) == 1 ? Users.Single( ).Field<UInt32>( "User_ID" ) : 0;
}
}
幸运的是,这个分支在引用Users
类的QatiTables
属性时很快就结束了,这已在上面详细描述过。这结束了我们在上面的Start方法中初始化While循环的开始。
下一个分支是PlayerCheck
类的Session
方法。在我们深入了解该方法之前,该方法本身旨在对数据库进行频繁检查,并在玩家加入和离开游戏时通知程序。对于我的问题,这种方法也可能被认为是我第二次最可能的怀疑。
private async Task PlayerCheck( ) {
List<Task> TList = new List<Task>( );
IEnumerable<Player> Candidates = ( await QatiTables.Players.FreshAsync( ) ).AsEnumerable(
).Select<DataRow, Player>( Row => new Player( Row.Field<UInt32>( "participant_id" ), this.SessionID ) );
Candidates.Where( P =>
!( PlayerStatus.BLOCKED | PlayerStatus.KICKED | PlayerStatus.QUIT | PlayerStatus.LOGOFF ).HasFlag( P.Status ) &&
!this._Players.Contains( P ) ).ToList( ).ForEach( P => {
this._Players.Add( P );
TList.Add( Session.OnPlayerJoined( P ) );
} );
Candidates.Where( P =>
( PlayerStatus.BLOCKED | PlayerStatus.KICKED | PlayerStatus.QUIT | PlayerStatus.LOGOFF ).HasFlag( P.Status ) &&
this._Players.Contains( P ) ).ToList( ).ForEach( P => {
this._Players.Remove( P );
TList.Add( Session.OnPlayerLeft( P ) );
} );
await Task.WhenAll( TList );
}
在此方法中,我们首先引用了Players
类的QatiTables
属性:
public static SqlTableAsync Players {
get {
try {
return QatiTables.Tables[7] ?? ( QatiTables.Tables[7] = new SqlTableAsync(
"QAndATimeServer.txt",
"session_participants",
"session_id = @param_val_0",
new object[ ] { QatiTables.GameSessions.Fresh.AsEnumerable( ).Where( Row =>
!QatiTables.GameEndedFilter.Contains( Row.Field<string>( "status" ) )
).Single( ).Field<UInt32>( "game_session_id" ) } ) );
} catch ( Exception ex ) {
ex.Report( );
return null;
}
}
}
此方法引用我们现在熟悉的SqlTableAsync
类。回过头来看,我们看到Session.PlayerCheck
方法引用了FresAsync( )
类的SqlTableAsync
函数:
/// <summary>
/// Get fresh table asynchronously.
/// </summary>
/// <returns>Refreshed Table</returns>
public async Task<DataTable> FreshAsync( ) {
await this.TableLib.ReadTables( );
return this.TableLib.Tables[this.TableName];
}
此方法与Fresh
属性相同,只是它已使用async
关键字标记。
回到Session.PlayerCheck
方法,我们看到该方法使用数据行的QatiTables.Players
和Player
类对象选择session_participant_id
行。会话类的SessionID
:
/// <summary>
/// Create new Player object.
/// </summary>
/// <param name="PlayerID">Player ID</param>
/// <param name="SessionID">ID of session player is in.</param>
public Player( UInt32 PlayerID, UInt32 SessionID ) {
this.PlayerID = PlayerID;
this.SessionID = SessionID;
}
返回时,该部分会根据当前游戏中存在并处于活动状态的条件(Candidates
,IEnumerable<Player>
等)过滤我们的{{1}} BLOCKED
并且它们目前在我们当前的会议中没有被考虑。这使用KICKED
属性...
Player.Status
...利用 /// <summary>
/// Get player status.
/// </summary>
public PlayerStatus Status {
get {
switch ( this.FreshRow.Field<string>( "status" ) ) {
/*Converts the string into an appropriate player status*/
}
}
}
属性......
Player.FreshRow
......实际上是异常来临的地方!这是private DataRow FreshRow {//Retrieve a fresh row from which data may be extracted.
get {
if ( this.PlayerID == 0 || this.SessionID == 0 )
throw new ObjectDisposedException( "Player", "Illegal attempt to access disposed Player object." );
try {
return QatiTables.Players.Fresh.AsEnumerable( ).Single( Row =>
Row.Field<UInt32>( "participant_id" ).Equals( this.PlayerID ) &&
Row.Field<UInt32>( "session_id" ).Equals( this.SessionID ) );
} catch ( Exception EX ) {
EX.Report( );
return null;
}
}
}
抛出异常的地方,说这个数据表中存在两个符合提供标准的行!我们做了!!!此属性还引用Single( Row => ... ... )
类的Fresh
属性的Players
属性,QatiTables
类对象,我们现在应该熟悉它。
呼!!!
为了完整,SqlTableAsync
类的引用_Players
值只是Session
类对象的列表,我们也引用了静态Player
方法:
Session.OnPlayerJoined( Player )
如果事件以异步方式存在,则此方法将调用该事件。没什么好看的,至少,我不这么认为。
因此结束private static async Task OnPlayerJoined( Player P ) {
if ( Session._PlayerJoined != null )
await Task.Run( ( ) => Session._PlayerJoined( Session.Instance, P ) );
}
Candidates
的第一个过滤器。下一个过滤器做了非常相似的事情;它根据玩家已从游戏中移除的标准(IEnumerable
,BLOCKED
等)过滤玩家,并且目前已计入,即在此会话的玩家列表中。
另一条信息:Player类以下列方式实现KICKED
接口:
IEquatable<Player>
这会将我们带到public class Player : IDisposable, IEquatable<Player> {
/*Code omitted*/
/// <summary>
/// Check if this player is equivalent to another player.
/// </summary>
/// <param name="other">Player with which to compare this player.</param>
/// <returns>PlayerID.Equals(other.PlayerID)</returns>
public bool Equals( Player other ) { return this.PlayerID.Equals( other.PlayerID ); }
}
方法中的分支的末尾,然后返回到PlayerCheck
方法。树中剩下的唯一其他分支是Start
方法,出于所有意图和目的,它与Session.OnSessionReport( )
和OnPlayerJoined
方法相同:它异步调用事件处理程序,如果事件处理程序不为空。
现在我们已经遍历了树,我遇到的问题(如上所述)是这样的:当调用OnPlayerLeft
属性时,Player.Status
方法中的Single( Row => ... ... )
方法该类的属性抛出异常,告诉我有多行符合我要过滤的条件。这是荒谬的,因为我正在过滤该数据表的FreshRow
以获取该行。然而,不知何故,当我设法查看该表时,确实有两行,是两行符合提供的标准。然而,当我查看该表获取其信息的数据库上的表时,我只找到一个。
为什么会发生这种情况?
为了缩小问题范围,我将在PRIMARY KEY
和SqlTableAsync
类中实现对添加主键的支持。当SqlLibAsync
填充包含相同主键的多行时,这会导致异常。
为此,我已将DataTable
类的Fresh
属性和FreshAsync
方法更改为以下内容:
SqlTableAsync
也; public DataTable Fresh { get {
try {
this.TableLib.ReadTables( ).Wait( );
if (this.PrimaryKeyColumn != null)
this.TableLib.Tables[TableName].Constraints.Add( "PrimaryKey",
this.PrimaryKeyColumn.Select<string, DataColumn>( columnName => this.TableLib.Tables[TableName].Columns[columnName] ).ToArray( ),
true );
return this.TableLib.Tables[this.TableName];
} catch ( Exception EX ) {
EX.Report( );
return null;
}
} }
public async Task<DataTable> FreshAsync( ) {
await this.TableLib.ReadTables( );
if ( this.PrimaryKeyColumn != null )
this.TableLib.Tables[TableName].Constraints.Add( "PrimaryKey",
this.PrimaryKeyColumn.Select<string, DataColumn>( columnName => this.TableLib.Tables[TableName].Columns[columnName] ).ToArray( ),
true );
return this.TableLib.Tables[this.TableName];
}
类的构造函数使用SqlTableAsync
,其中包含主键应基于的列名称,并将该值分配给本地string[ ]
PrimaryKeyColumn
变量。现在我在string[ ]
属性上得到这个新的异常,告诉我表中已经存在一个匹配这个约束的约束。
什么
QatiTables.Players.Fresh
方法在调用该类的Fresh
方法时,应该在DataSet DS
类中转储SqlLibAsync
,这意味着表中不应存在任何键因为{em>刚刚创建,因为ReadTables
方法被称为 JUST PRIOR 以尝试分配该主要数据集键。
我需要咖啡......
答案 0 :(得分:0)
问题是如果找到除一行之外的任何数字,Single将抛出异常。使用FirstOrDefault并执行空检查 请在此处查看MSDN链接 +1为一组广泛的源代码,很棒的帖子!
编辑: 在审核之后,您的一个单一陈述不合适,请查看
public static SqlTableAsync Players { get; }
看起来单个需要将过滤器作为其lambda或将其作为第一个。
答案 1 :(得分:0)
好;在多次哭泣和咬牙切齿之后,ReadTables
班的SqlLibAsync
方法的简单改变结束了我的问题。怎么以及为什么,我不知道。我想把这个问题留给研究目的,希望将来有人可以向我解释究竟发生了什么,以及为什么这个简单的改变解决了我的痛苦噩梦。
/// <summary>
/// Asynchronously read tables for this SqlLibAsync instance.
/// </summary>
public async Task ReadTables( ) {
//this.DS = new DataSet( ); <-----This line of code has been moved...
using ( MySqlConnection MSQCon = new MySqlConnection( this.ConnectionString ) ) {
await MSQCon.OpenAsync( );
try {
this.DS = new DataSet( ); //<-----To here. Then, abra cadabra, my problem disappeared. What is this, I don't even...
foreach ( MySqlDataAdapter Adapter in this.Adapters ) {
Adapter.SelectCommand.Connection = MSQCon;
await Adapter.FillAsync( this.DS, Adapter.TableMappings.Cast<DataTableMapping>( ).First( ).SourceTable );
}
} catch ( Exception ex ) {
ex.Report( );
}
await MSQCon.CloseAsync( );
}
if ( this.DS.Tables.Count == 0 )
await this.ReadTables( );
}