c# - DataSet within class is not being refreshed asynchronously? -
tl;dr
sorry if question title misleading; it's work in progress try root of issue.
i making .single( row => ... ... )
call against .asenumerable( )
extension of datatable
throwing exception. looking table, there 2 rows present impossible because, looking @ source table (the 1 on data base datatable
should populated), seeing 1 of these rows.
in effort narrow down problem, assigning primary key table, generating impossible exception telling me primary key exists within table, should not case table should have been just created.
why happening?
did read:
i'd hoped in course of forming question have come upon solution in 1 of "talking colleague/cardboard cutout aha" moments; not much.
there quite bit of code going on here, , explain best able trying accomplish (i apologize, not know precisely of proper terminology trying describe).
i working on project component act intermediary between online data base , desktop application. portability primary concern, making component event driven.
it case there many things going on must run concurrently; such, have endeavoured implement component asynchronously (async
, await
).
the program depends on static class (which have constructed) contains number of datatables
.
before post code, should initial suspicion multiple asynchronous tasks running in parallel are... "crashing each other" somehow. not know if case certain, though.
i start believe source of problem , work way down through stack. attempt in depth-first manner sake of brevity. more can added later if necessary.
before trace, want post method primary suspect:
/// <summary> /// asynchronously read tables 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, troubles originating .cast<>() call? } } catch ( exception ex ) { ex.report( ); } await msqcon.closeasync( ); } if ( this.ds.tables.count == 0 ) await this.readtables( ); }
this method has given me quite bit of trouble , has gone through several iterations. in past receiving exception informing me connection attempt had been made while datareader
accessing connection, or something, believe because using multiple instances of class work asynchronously, , in improper fashion such work (basically, multiple connections single computer same database), went route seems have dealt issue, getting new issue. believe that, reason, parallel asynchronous tasks , method not getting along, lack know why.
the start of tree methods use, in context of having problems, here:
public static async task start( ) { /*i have omitted code appears 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*/ }
we start session.instance
private static session _instance; // private static reference session. /// <summary> /// static reference instance of session. /// </summary> public static session instance { { return session._instance; } }
next have thestatus
property of session
class:
private sessionstatus status { //retrieve fresh session status. { switch ( this.freshrow.field<string>( "status" ) ) { /*translates incoming string enum. #4*/ } } }
the status
property references freshrow property:
private datarow freshrow { //retrieves datarow fresh session information can extracted. (status, buzzedinuser, etc). { if ( this.sessionid == 0 ) throw new objectdisposedexception( "session", "illegal attempt access disposed session object." ); return qatitables.gamesessions.fresh.asenumerable( ).single( row => row.field<uint32>( "game_session_id" ).equals( this.sessionid ) ); } /*session.sessionid simple property getter , private setter.*/ }
from here have qatitables.gamesessions
property:
public static sqltableasync gamesessions { { return qatitables.tables[1] ?? ( qatitables.tables[1] = new sqltableasync( "qandatimeserver.txt", "game_sessions", "created_by = @param_val_0", //we have grab of these every time because row status may change mid-game , lose it. new object[ ] { qatitables.users.fresh.asenumerable( ).single( ).field<uint32>( "user_id" ) } ) ); } /*the referenced qatitables.tables[] array of sqltableasync objects.*/ }
this property makes use of sqltableasync
class:
/// <summary> /// select * [table] [filter] /// </summary> /// <param name="serverfile">ftp file containing database connection data.</param> /// <param name="tablename">table retrieve.</param> /// <param name="filter">query filter</param> /// <param name="parameters">parameters on 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 custom extension method exceptions. #5*/ } this.tablename = tablename; }
from within constructor have sqllibasync
class:
/// <summary> /// initialize new sqllib /// </summary> /// <param name="databaseconnection">text file read database connection parameters on file server</param> public sqllibasync( string databaseconnection ) { this.connectionfile = databaseconnection; } /// <summary> /// set ftp file read connection data. /// </summary> private string connectionfile { set { /*this populates local variables information file read directly ftp server. such, have omitted code functions should, , contains sensitive bits of information.*/ } }
we've reached end of our first branch! hooray!
going up, our next referenced method sqllibasync.gettable( )
method:
/// <summary> /// select * [table] [filter] /// </summary> /// <param name="table">table name</param> /// <param name="filter">query filter</param> /// <param name="parameters">object of parameters 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 } ); }
this method makes reference sqllibasync.gettables( ... ) method:
/// <summary> /// return multiple tables data set. /// </summary> /// <param name="tables">tables 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 * " + table + " "; if ( string.isnullorempty( filter ) ) query += "1;"; else query += filter + ";"; mysqldataadapter adapter = new mysqldataadapter( new mysqlcommand( query ) { commandtype = commandtype.text } ); if ( parameterset != null ) ( 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( ); }
this method makes use of first described sqllibasync.readtables
method. brings end of our second branch.
we rescind qatitables.gamesessions
property reference qatitables.users
property:
public static sqltableasync users { { return qatitables.tables[0] ?? ( qatitables.tables[0] = new sqltableasync( "qandatimeserver.txt", "users", "last_ip_address = @param_val_0 , role in (@param_val_1, @param_val_2) , is_login = @param_val_3", new object[ ] { methods.ipaddress, "admin", "entertainer", 1 } ) ); } }
this property references static methods.ipaddress
property, omit feel context in used defines sufficiently.
this property makes use of sqltablesasync
class, it's methods , properties, described previously.
this property brings end of branch.
the next branch fresh
property of sqltableasync
class, referenced in freshrow
property of session
class:
/// <summary> /// fresh table. /// </summary> public datatable fresh { { try { this.tablelib.readtables( ).wait( ); return this.tablelib.tables[this.tablename]; } catch ( exception ex ) { ex.report( ); return null; } } }
this makes reference tablelib
variable; local sqltableasync
class object. makes reference readtables
method of class (described previously), tables
property of class:
/// <summary> /// sqllibs table set. /// </summary> public datatablecollection tables { { return this.ds.tables; } }
this property makes reference class ds
variable, dataset
described in previous methods.
and finally, we've reached end of our first major branch, status
property of session
class, , of it's related classes, properties, methods , functions, brings our next major branch: userid property of session class:
private static uint32 userid { // static logged in userid { ienumerable<datarow> users = qatitables.users.fresh.asenumerable( ); //to avoid multiple unnecessary queries. return users.count( ) == 1 ? users.single( ).field<uint32>( "user_id" ) : 0; } }
fortunately, branch ends references users
property of qatitables
class, has been described in detail above. ends start of our initial while loop in start method above.
the next branch playercheck
method of session
class. before meat of method, method designed run frequent checks against database , inform program when players join , leave game. method may considered second suspect cause of problem.
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 ); }
within method have first referenced players
property of qatitables
class:
public static sqltableasync players { { 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; } } }
this method references sqltableasync
class familiar. going up, see session.playercheck
method referencing fresasync( )
function of sqltableasync
class:
/// <summary> /// fresh table asynchronously. /// </summary> /// <returns>refreshed table</returns> public async task<datatable> freshasync( ) { await this.tablelib.readtables( ); return this.tablelib.tables[this.tablename]; }
this method identical fresh
property, has been tagged async
keyword.
moving session.playercheck
method, see method selecting qatitables.players
rows collection of player
class objects using session_participant_id
of data row , sessionid
of session class:
/// <summary> /// create new player object. /// </summary> /// <param name="playerid">player id</param> /// <param name="sessionid">id of session player in.</param> public player( uint32 playerid, uint32 sessionid ) { this.playerid = playerid; this.sessionid = sessionid; }
going up, part filters our candidates
ienumerable<player>
on criteria present , active within current game (not blocked
, kicked
, etc) , not presently accounted within our current session. makes use of player.status
property...
/// <summary> /// player status. /// </summary> public playerstatus status { { switch ( this.freshrow.field<string>( "status" ) ) { /*converts string appropriate player status*/ } } }
...which makes use of player.freshrow
property...
private datarow freshrow {//retrieve fresh row data may extracted. { if ( this.playerid == 0 || this.sessionid == 0 ) throw new objectdisposedexception( "player", "illegal attempt 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; } } }
... exception coming!!! single( row => ... ... )
throwing exception, saying there exists 2 rows within data table match provided criteria!!! made it!!! property makes reference fresh
property of players
property of qatitables
class, sqltableasync
class object should familiar.
phew!!!
for sake of being complete, referenced _players
value of session
class list of player
class objects, , have referenced static session.onplayerjoined( player )
method:
private static async task onplayerjoined( player p ) { if ( session._playerjoined != null ) await task.run( ( ) => session._playerjoined( session.instance, p ) ); }
this method invokes event if exists in asynchronous fashion. nothing fancy going on here, @ least, don't think so.
thus ends first filter of candidates
ienumerable
. next filter similar; filters players on criteria have been removed game (blocked
, kicked
, etc), , presently accounted for, is, within list of players session.
one other piece of information: player class implements iequatable<player>
interface in following fashion:
public class player : idisposable, iequatable<player> { /*code omitted*/ /// <summary> /// check if player equivalent player. /// </summary> /// <param name="other">player compare player.</param> /// <returns>playerid.equals(other.playerid)</returns> public bool equals( player other ) { return this.playerid.equals( other.playerid ); } }
this takes end of branch within playercheck
method, , start
method. other branch left in tree session.onsessionreport( )
method which, intents , purposes, identical onplayerjoined
, onplayerleft
methods: calls event handler asynchronously, if event handler not null.
now we've traversed tree, problem having (as described above in brief) this: when calling player.status
property, single( row => ... ... )
method within freshrow
property of class throwing exception, telling me there multiple rows match criteria on filtering. absurd because filtering on primary key
of data table row. yet, somehow, when managed @ table, sure enough, there two, yes two, rows matched provided criteria. yet, when looked @ table on data base table gets information, find one.
why happening?
edit
in effort narrow down issue, going implement sqltableasync
, sqllibasync
classes support adding primary keys. should result in exception when datatable
populated multiple rows containing same primary key.
to end have changed fresh
property , freshasync
methods of sqltableasync
class following:
public datatable fresh { { 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]; }
also; constructor of sqltableasync
class takes string[ ]
column names on primary key should based , assigns value local primarykeycolumn
string[ ]
variable. getting new exception on qatitables.players.fresh
property telling me there exists constraint within table matches constraint.
what
the fresh
method should dumping dataset ds
within sqllibasync
class when calls readtables
method of class, meaning no keys should exist within tables of data set has just been created because readtables
method being called just prior trying assign primary key.
i need coffee...
the issue single throw exception if number other 1 row found. use firstordefault , null check see msdn link here +1 extensive set of source code, nice post!
edit: after review 1 of single statements out of place, check out
public static sqltableasync players { get; }
it looks single need take in filter lambda or make first.
Comments
Post a Comment