I am making a databasehelper class with methods to access a SQLCE database. I want to use the same method to read row(s) using different classes containing properties that match the fields in the different tables. The class to be used is determined during runtime and I want to pass a list with objects from the class on to the method and get the propertynames and use them to read the database. Would be very handy because I could use it for all my (SQLCE-)databases.
(I updated the erroneous code in order to provide the solution here)
#region ReadData
///----------------------------------------------------------------------
/// <summary>
/// Reads datarows from database and adds them to list.
/// </summary>
/// <param name="data">List containing objects with properties.</param>
/// <param name="table">Table in database.</param>
/// <param name="search">Substring of SQL-statement that follows 'WHERE'.</param>
/// <param name="connect">Connectionstring.</param>
/// <returns>true if successfull</returns>
///----------------------------------------------------------------------
public static bool ReadData<T>(List<T> data, string table, string search, string connect) where T : class, new()
{
// Return if input id missing
if (data == null || table == "" || connect == "") return false;
// retrieve properties from Data
PropertyInfo[] propinf = typeof(T).GetProperties();
// Create string with SQL-statement
string fields = "";
// retrieve fields from propinf
foreach (PropertyInfo p in propinf)
{
fields += fields == "" ? p.Name : ", " + p.Name;
}
// create SQL SELECT statement with properties and search
string sql = "SELECT " + fields + " FROM " + table;
sql += search == "" ? "" : " WHERE " + search;
// Instantiate and open database
SqlCeConnection cn = new SqlCeConnection(connect);
if (cn.State == ConnectionState.Closed)
cn.Open();
data.Clear(); // just in case
try
{
SqlCeCommand cmd = new SqlCeCommand(sql, cn);
cmd.CommandType = CommandType.Text;
SqlCeResultSet rs = cmd.ExecuteResultSet(ResultSetOptions.Scrollable);
if (rs.HasRows) // Only if database is not empty
{
while (rs.Read()) // read database
{
// instantiate single item of list Data
var dataitem = new T();
int ordinal = 0;
foreach (PropertyInfo p in propinf)
{
// read database and
PropertyInfo singlepropinf = typeof(T).GetProperty(p.Name);
ordinal = rs.GetOrdinal(p.Name);
singlepropinf.SetValue(dataitem, rs.GetValue(ordinal), null); // fill data item
}
data.Add(dataitem); // and add it to data.
}
}
else
{
MessageBox.Show("No records matching '" + search + "'!");
return false;
}
}
catch (SqlCeException sqlexception)
{
MessageBox.Show(sqlexception.Message, "SQL-error.", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error.", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
finally
{
cn.Close();
}
return true;
}
#endregion
I had two questions:
1) how do I pass this list with unknown type? Answers I found so far did not help me solve this issue.
2) how do I instantiate an object of unknown-type class (at compile-time) in order to add it to the List without causing a compile-error?
Thanks very much!
Below an update to the code. Its getting close to final and has been tested in all kinds of different situations. Ideally iterations using reflection would have to be replaced by something less performance-intense but as long as database operations are way more time-consuming I guess it doesn't really matter in real life. I'm already quite happy with it.
1: a list of unknown type could be the non-generic
IList
, orArrayList
, orList<object>
2:
Activator.CreateInstance(type)
Alternatively, look at writing a generic method, ideally something like:
and use
new T()
to create new items, andtypeof(T)
to talk about theType
. With a generic method the caller supplies the T - often implicitly. Note that there is no need for theref
in your example.