Clean Design to Handle Multiple Result Sets from IDataReader - c#

Maybe SO isn't the place for this, and I apologize if it's not, but I can't help but think there's a better way to do this. This just seems like a gross and not very clean way of checking which result set I'm on and then performing a corresponding action. Anyone have any suggestions? (Please ignore the fact I'm always returning null).
public MemberDto Load(long entityId)
{
using (var cn = new SqlConnection(#"connectionstringstuff"))
{
cn.Open();
using (SqlCommand cm = cn.CreateCommand())
{
cm.CommandText = "Client.[MemberGet]";
cm.CommandType = CommandType.StoredProcedure;
cm.Parameters.AddWithValue("#EntityId", entityId);
using (IDataReader dr = cm.ExecuteReader())
{
var memberModel = new MemberDto();
do
{
while (dr.Read())
{
var sdr = new SafeDataReader(dr);
var firstColumn = sdr.GetName(0);
if (firstColumn.StartsWith("Attribute"))
{
AddAttribute(memberModel, sdr);
}
else if (firstColumn.StartsWith("AlternateId"))
{
AddAlternateId(memberModel, sdr);
}
else
{
memberModel.ClientId = sdr.GetInt64("ClientId");
memberModel.Id = sdr.GetInt64("EntityId");
memberModel.Name = sdr.GetString("EntityName");
}
}
} while (dr.NextResult());
}
}
}
return null;
}
private void AddAttribute(MemberDto model, SafeDataReader reader)
{
model.Attributes.Add(
reader.GetInt32("AttributeTypeId").As<EntityAttributeType>(),
reader.GetString("Value"));
}
private void AddAlternateId(MemberDto model, SafeDataReader reader)
{
model.Attributes.Add(
reader.GetInt32("AlternateIdTypeId").As<EntityAttributeType>(),
reader.GetString("Value"));
}

The way that we approach this is not to loop on the nextresult, but explicitly code it to match the underlying data. It is somewhat more visually obvious what the expected results are with this approach (in my opinion, of course).
Here is an example rewrite, assuming the order is membermodel, attributes, and alternates.
while (dr.Read())
{
var sdr = new SafeDataReader(dr);
memberModel.ClientId = sdr.GetInt64("ClientId");
memberModel.Id = sdr.GetInt64("EntityId");
memberModel.Name = sdr.GetString("EntityName");
}
if (dr.NextResult())
{
while (dr.Read())
{
AddAttribute(memberModel, new SafeDataReader(dr));
}
}
if (dr.NextResult())
{
while (dr.Read())
{
AddAlternateId(memberModel, new SafeDataReader(dr));
}
}

Perhaps all sp that return multiple resultsets should always include first a resultset specifiying which resultsets are returned in which order:
dr = multRsCmd.ExecureReader();
// first rs is always the meta
List<string> resultSetIds = new List<string>();
while (dr.Read())
resultSetIds.Add(dr[0]
foreach (string rsId in resultSetIds)
{
if (!dr.NextResult())
break; // or throw, should not happen
if (rsId == "ClientDataWithAttribute")
{
// code to handle the exact rs layout for ClientDataWithAttribute
//
//
}
else if (rsId == "ClientDataWithAltId")
{
// code to handle the exact rs layout for ClientDataWithAltId
//
//
}
else if (rsId == "ClientData")
{
// code to handle the exact rs layout for ClientData
//
//
}
}
This of course screams out for refactoring:
dr = multRsCmd.ExecureReader();
// first rs is always the meta
List<string> resultSetIds = new List<string>();
while (dr.Read())
resultSetIds.Add(dr[0]
foreach (string rsId in resultSetIds)
{
if (!dr.NextResult())
break; // or throw, should not happen
// pull subclass out of registry of readers
ResultSetReader rsr = ResultSetReaders.Find(rsId);
// subclass cleanly holds the layout-dependent logic
memberModel = rsr.FromDataReader(dr);
}
That looks pretty clean, but you could even add default behavior in the ResultSetReader to drive the DTO property mapping from a config file or other source and only override when you get special conditions. You get the idea.

Related

How to store result from mysql query

How do I store the results from a mysql query for use in other classes most efficiently?
I've tried the following code, which executes properly and stores all data in reader as it should. Reading the DataReader here works fine if I want to!
public class DatabaseHandler
{
public void MySqlGetUserByName(string input_username, MySqlDataReader reader)
{
try
{
_database.Open();
string query = "SELECT * FROM users WHERE username = '#input'";
MySqlParameter param = new MySqlParameter(); param.ParameterName = "#input"; param.Value = input_username;
MySqlCommand command = new MySqlCommand(query, _database);
command.Parameters.Add(param);
reader = command.ExecuteReader();
_database.Close();
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
But when I try to read the same DataReader here, it is null and throws an exception (right after Debug6).
public class LoginHandler
{
public static void UserAuth(Client user, string input_username, string input_password)
{
DatabaseHandler dataBase = new DatabaseHandler();
MySqlDataReader dataReader = null;
dataBase.MySqlGetUserByName(input_username, dataReader);
Console.WriteLine("Debug6");
if (!dataReader.HasRows)
{
user.SendChatMessage("No match found.");
return;
}
while (dataReader.Read())
{
user.SetData("ID", (int)dataReader[0]);
user.SetData("username", (string)dataReader[1]);
user.SetData("email", (string)dataReader[2]);
user.SetData("password", (string)dataReader[3]);
}
dataReader.Close();
}
}
Please let me know how to make this work, or if there is a more efficient way of doing this without limiting the function of MySqlGetUserByName. The purpose of it is to input a name and a place to store all info from the match in the database.
Also, feel free to drop in any other suggestions that could make the code more efficient.
You could change your MySqlGetUserByName to return a User instance if all goes well, otherwise you return a null instance to the caller (Or you can thrown an exception, or you can set a global error flag in the DatabaseHandler class..., but to keep things simple I choose to return a null)
public class DatabaseHandler
{
public User MySqlGetUserByName(string input_username)
{
User result = null;
try
{
string query = "SELECT * FROM users WHERE username = #input";
using(MySqlConnection cnn = new MySqlConnection(......))
using(MySqlCommand command = new MySqlCommand(query, cnn))
{
cnn.Open();
command.Parameters.AddWithValue("#input", input_username);
using(MySqlDataReader dataReader = command.ExecuteReader())
{
if (dataReader.Read())
{
result = new User();
result.ID = Convert.ToInt32(dataReader[0]);
..... and so on with the other user properties ....
}
}
}
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
// Return the user to the caller. If we have not found the user we return null
return result;
}
}
In the same way the caller handles the situation
public class LoginHandler
{
public static void UserAuth(string input_username, string input_password)
{
DatabaseHandler dataBase = new DatabaseHandler();
User result = dataBase.MySqlGetUserByName(input_username);
// If we have not found the user we have a null in the variable
if(result == null)
{
// Send your message using a static method in the user class
// User.SendMessage("User with username {input_username} not found!");
}
else
{
// User ok. return it? or do something with its data?
}
}
}

Get list/multiset from informix to C#

I'm making a library for connecting an Informix database with a C# extension for use in Outsystems.
So, now I've hit a wall. I need to receive a list/multiset from the db. How can I do that? I'm using the IfxDataReader to receive the data. But I see no method that could work.
Here is how we can send a list/multiset input parameter. But I need to receive it from a result set.
EDIT: Seeing as this was frowned upon by someone I'll provide some code and my try at it to see if you think it's correct (can't test it right now as I don't yet have data on the database... I'll test it in the end):
ssBensAndFotos = new RLBensAndFotos_BemRecordList();
DatabaseConnection dc = new DatabaseConnection(ssDatabase, ssHost, ssServer, ssService, ssProtocol, ssUserID, ssPassword);
try
{
dc.Conn = new IfxConnection(dc.ConnectionString);
dc.Cmd = new IfxCommand("get_bens_and_fotos_by_id_diligencia", dc.Conn);
dc.Cmd.CommandType = CommandType.StoredProcedure;
dc.Cmd.Parameters.Add(new IfxParameter("pi_id_diligencia", IfxType.Integer) { Direction = ParameterDirection.Input, Value = sspi_id_diligencia });
dc.Conn.Open();
using (IfxDataReader reader = dc.Cmd.ExecuteReader())
if (reader.HasRows)
while (reader.Read())
{
var bensAndFotos = new STBensAndFotos_BemStructure()
{
ssid_bem = reader.GetInt32(0),
ssnumero = reader.GetInt32(1),
ssespecie = reader.GetString(2),
ssbem = reader.GetString(3),
ssvalor = reader.GetDecimal(4),
sscomum = reader.GetInt32(5),
ssvoice_record = AuxiliaryMethods.CreateByteArrayFromIfxBlob(reader.GetIfxBlob(6))
};
// Here I get the string, split it and check to see which members of the array are integers, since in this case I'll be getting a multiset of int's
var multisetString = reader.GetString(7).Split('\'');
int n;
foreach (var item in multisetString)
if (int.TryParse(item, out n))
bensAndFotos.ssfotos.Add(new STBensAndFotos_FotoStructure() { ssfoto = n });
ssBensAndFotos.Add(bensAndFotos);
}
dc.Conn.Close();
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
LIST, MULTISET and SET are mapped to String. You should be able to use IfxDataReader.GetString() to get the data.

C# and Reflection don't work twice in a row

I've a problem and I can't figured it out how to solve it.
I've a class for fetching data from a Database, in this class I've a method for a simple select * this method is called
List<T> All<T>(string tableName)
and you have to specify which resource you want to fetch, for example
All<User>("users")
And, aside from the classic SQL Reader and SQL Command, the core of the method is this
public override List<T> All<T>(string resource)
{
List<T> result = new List<T>();
using (MySqlConnection sqlConnection = new MySqlConnection(connectionString))
{
sqlConnection.Open();
try
{
string query = "SELECT * FROM " + resource + " WHERE 1=1";
using (MySqlCommand sqlCommand = new MySqlCommand(query, sqlConnection))
{
lock (locker)
{
MySqlDataReader reader = sqlCommand.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
T model = Activator.CreateInstance<T>();
Dictionary<string, object> _properties = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
string property = reader.GetName(i);
object value = reader.GetValue(i);
_properties.Add(property, value);
}
var type = model.GetType();
var method = type.GetMethod("SetProperties");
var invoked = method.Invoke(model, new object[] { _properties });
result.Add(model);
}
}
reader.Close();
}
}
}
catch (Exception ex)
{
Program.eventLogger.Add(new Event(EventType.Error, "SQL Data Providers", "Exception catched on All", ex));
}
finally
{
sqlConnection.Close();
}
}
return result;
}
Basically, based on the Type from the method header, the method will try to create an new instance of the specific type, later for each field from the query, it will fills all the attributes of the class on a temporaneous list. Once it's done it will try to call the method "SetProperties" which basically set every attributes of the class using reflection.
This is the core of SetProperties, equal for each entity:
public virtual bool SetProperties(Dictionary<string,object> properties)
{
if(this.ValidateData(properties))
{
FillNullableAttributes(properties);
// Iterate trough every key : value pairs in properties
foreach (KeyValuePair<string, object> kvp in properties)
{
if (this.data.Contains(kvp.Key))
{
var property = this.GetType().GetProperty(kvp.Key);
PropertyInfo propertyInfo = this.GetType().GetProperty(kvp.Key);
// Set the current fetched key with the given value if !null
if (kvp.Value != null)
{
Type fetchedType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
object safeConversion = (kvp.Value == null || kvp.Value == DBNull.Value) ? null : Convert.ChangeType(kvp.Value, fetchedType);
if (propertyInfo.CanWrite)
{
propertyInfo.SetValue(this, safeConversion, null);
}
}
}
}
return true;
}
return false;
}
In conclusion the result, which is a list, will be returned and the specific Entity will have its own BindingList filled. The binding list, for each entity is described as follow:
public static BindingList<Seller> items = new BindingList<Seller>();
This code works fine, even if there's a lot of space for improvements I know, but if I called it twice like this:
User.items = new BindingList<User>(provider.All<User>("users"));
User.items = new BindingList<User>(provider.All<User>("users"));
The second list will be filled by empty entities, the counting of the will be correct but they will be empties... and that shouldn't occurs.
The only thing that I figured it out, from the debugging, is that on the second call
var invoked = method.Invoke(model, new object[] { _properties });
invoked is set to false.
The result of:
var invoked = method.Invoke(model, new object[] { _properties });
Is the return value from your SetProperties method, not whether the method was invoked as your question indicates. Your SetProperties method is telling you that it was unable to do its work, debug that and you will find your answer.

Parameter '?user_email' not found in the collection

I am using MySql 5.6x with Visual Studio 2015, windows 10, 64-bit. C# as programming language. In my CRUD.cs (Class file) i have created the following method:
public bool dbQuery(string sql,string[] paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null){
foreach(string i in paramList){
string[] valus = i.Split(',');
string p = valus[0];
string v = valus[1];
cmd.Parameters[p].Value = v;
}
}
if (cmd.ExecuteNonQuery() > 0)
{
flag = true;
}
}
catch (Exception exc)
{
error(exc);
}
}
I am passing the query and Parameters List like this:
protected void loginBtn_Click(object sender, EventArgs e)
{
string sql = "SELECT * FROM dept_login WHERE (user_email = ?user_email OR user_cell = ?user_cell) AND userkey = ?userkey";
string[] param = new string[] {
"?user_email,"+ userid.Text.ToString(),
"?user_cell,"+ userid.Text.ToString(),
"?userkey,"+ userkey.Text.ToString()
};
if (db.dbQuery(sql, param))
{
msg.Text = "Ok";
}
else
{
msg.Text = "<strong class='text-danger'>Authentication Failed</strong>";
}
}
Now the problem is that after the loop iteration complete, it directly jumps to the catch() Block and generate an Exception that:
Parameter '?user_email' not found in the collection.
Am i doing this correct to send params like that? is there any other way to do the same?
Thanks
EDIT: I think the best way might be the two-dimensional array to collect the parameters and their values and loop then within the method to fetch the parameters in cmd.AddWidthValues()? I may be wrong...
In your dbQuery you don't create the parameters collection with the expected names, so you get the error when you try to set a value for a parameter that doesn't exist
public bool dbQuery(string sql,string[] paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null){
foreach(string i in paramList){
string[] valus = i.Split(',');
string p = valus[0];
string v = valus[1];
cmd.Parameters.AddWithValue(p, v);
}
}
if (cmd.ExecuteNonQuery() > 0)
flag = true;
}
catch (Exception exc)
{
error(exc);
}
}
Of course this will add every parameter with a datatype equals to a string and thus is very prone to errors if your datatable columns are not of string type
A better approach would be this one
List<MySqlParameter> parameters = new List<MySqlParameter>()
{
{new MySqlParameter()
{
ParameterName = "?user_mail",
MySqlDbType= MySqlDbType.VarChar,
Value = userid.Text
},
{new MySqlParameter()
{
ParameterName = "?user_cell",
MySqlDbType= MySqlDbType.VarChar,
Value = userid.Text
},
{new MySqlParameter()
{
ParameterName = "?userkey",
MySqlDbType = MySqlDbType.VarChar,
Value = userkey.Text
},
}
if (db.dbQuery(sql, parameters))
....
and in dbQuery receive the list adding it to the parameters collection
public bool dbQuery(string sql, List<MySqlParameter> paramList= null)
{
bool flag = false;
try
{
connect();
cmd = new MySqlCommand(sql,con);
cmd.Prepare();
if(paramList != null)
cmd.Parameters.AddRange(paramList.ToArray());
if (cmd.ExecuteNonQuery() > 0)
{
flag = true;
}
}
catch (Exception exc)
{
error(exc);
}
}
By the way, unrelated to your actual problem, but your code doesn't seem to close and dispose the connection. This will lead to very nasty problems to diagnose and fix. Try to use the using statement and avoid a global connection variable
EDIT
As you have noticed the ExecuteNonQuery doesn't work with a SELECT statement, you need to use ExecuteReader and check if you get some return value
using(MySqlDataReader reader = cmd.ExecuteReader())
{
flag = reader.HasRows;
}
This, of course, means that you will get troubles when you want to insert, update or delete record where instead you need the ExecuteNonQuery. Creating a general purpose function to handle different kind of query is very difficult and doesn't worth the work and debug required. Better use some kind of well know ORM software like EntityFramework or Dapper.
Your SQL Commands' Parameters collection does not contain those parameters, so you cannot index them in this manner:
cmd.Parameters[p].Value = v;
You need to add them to the Commands' Parameters collection in this manner: cmd.Parameters.AddWithValue(p, v);.

How to read an enumerable without a loop without losing its scope?

I'm facing a pretty weird construct. The Foo type returned in an IEnumerable loses its data as soon as the enumeration ends. This means that I can't do a enumeration.First() because the data would be lost right away.
A loop over it works, but since I know it will contain only a single element that would be weird.
int Test(out int something)
IEnumerable<Foo> enumeration = ...
for (var foo in enumeration) {
something = foo.GetSomething ();
return foo.GetAnInt ();
}
something = 42;
return 0;
}
Another way I though of is abusing a Linq Select, but that's just as horrible.
Is there a way to work around this limitation? Fixing the root cause is obviously superior, but difficult in this case.
Edit: It's an IEnumerable<IDataRecord> that is yield returned from a transactioned SQL data reader.
public IEnumerable<IDataRecord> ExecuteReader (SqlCommand cmd)
{
using (var con = GetConnection()) {
con.Open ();
using (var tr = con.BeginTransaction ()) {
cmd.Connection = con;
var reader = cmd.ExecuteReader ();
while (reader.Read ()) {
yield return reader;
}
tr.Commit ();
}
}
}
The problem is that your ExecuteReader method does simply return the SqlDataReader itself (which implements IDataRecord), instead of returning a block of data. So when you do this:
var list = ExecuteReader(...).ToList();
In that case all elements of the list will be the same SqlDataReader instance, but after the ToList has been executed, the reader has been closed. I'm a bit surprised that you don't get an ObjectDisposedException.
For this to work, you need to return a copy of the data in the IDataRecord. You think you can iterate the elements in the data record. An other option is to change the ExecuteReader to the following:
public IEnumerable<T> ExecuteReader<T>(SqlCommand cmd,
Func<IDataRecord, T> recordCreator)
{
using (var con = GetConnection()) {
con.Open ();
using (var tr = con.BeginTransaction()) {
cmd.Connection = con;
var reader = cmd.ExecuteReader();
while (reader.Read()) {
yield return recordCreator(reader);
}
tr.Commit();
}
}
}
This way you can do the following:
var list = ExecuteReader(command, record => new
{
Item1 = record.GetInt("id"),
Item2 = record.GetString("name")
});
Note: I'm not sure why you need a transaction for this anyway.
How about
int Test(out int something)
{
IEnumerable<Foo> enumeration = ...
var values = enumeration
.Select(foo => new
{
something = foo.GetSomething(),
anInt = foo.GetAnInt()
})
.FirstOrDefault();
if (values != null)
{
something = values.something;
return values.anInt;
}
else
{
something = 42;
return 0;
}
}
GetSomething and GetAnInt are called while inside the enumeration.
Another idea could be to convert the result type of the method from IEnumerable to IEnumerator. That way, scope control is much easier, and returning single results does not require any (fake) loop, too.
Edit: I think I found a way to refactor the whole issue. This circumvents the initial problem by using new disposable class that contains the logic formerly found in the method. It's very readable and less code even.
public TransactedConnection GetConnection (string text)
{
return new TransactedConnection (_ConnectionString, text);
}
public class TransactedConnection : IDisposable
{
private readonly SQLiteCommand _Cmd;
private readonly SQLiteConnection _Con;
private readonly SQLiteTransaction _Tr;
public TransactedConnection (string connection, string text)
{
_Cmd = new SQLiteCommand (text);
_Con = new SQLiteConnection (connection);
_Con.Open ();
_Cmd.Connection = _Con;
_Tr = _Con.BeginTransaction ();
}
public void Dispose ()
{
_Tr.Commit ();
_Tr.Dispose ();
_Con.Dispose ();
_Cmd.Dispose ();
}
public SQLiteParameterCollection Parameters
{
get
{
return _Cmd.Parameters;
}
}
public int ExecuteNonQuery ()
{
return _Cmd.ExecuteNonQuery ();
}
public object ExecuteScalar ()
{
return _Cmd.ExecuteScalar ();
}
public SQLiteDataReader ExecuteReader ()
{
return _Cmd.ExecuteReader ();
}
}
public void Test (string match)
{
var text = "SELECT * FROM Data WHERE foo=#bar;";
using (var cmd = GetConnection (text)) {
cmd.Parameters.Add ("#bar", DbType.String).Value = match;
using (var reader = cmd.ExecuteReader ()) {
while (reader.Read ()) {
Console.WriteLine (reader["foo"]);
}
}
}
}

Categories

Resources