我有一些表示数据库表的类,要在DataGridView
上加载每个表的行,我有一个List<>
函数,它在循环中获取该表中的所有行。
public List<class_Table1> list_rows_table1()
{
// class_Table1 contains each column of table as public property
List<class_Table1> myList = new List<class_Table1>();
// sp_List_Rows: stored procedure that lists data
// from Table1 with some conditions or filters
Connection cnx = new Connection;
Command cmd = new Command(sp_List_Rows, cnx);
cnx.Open;
IDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
class_Table1 ct = new class_Table1();
ct.ID = Convert.ToInt32(dr[ID_table1]);
ct.Name = dr[name_table1].ToString();
//... all others wanted columns follow here
myList.Add(ct);
}
dr.Close();
cnx.Close();
// myList contains all wanted rows; from a Form fills a dataGridView
return myList();
}
对于其他表,还有其他一些函数:list_rows_table2,list_rows_table3 ...
我的问题是:如何创建唯一的List<>
函数,我可以在其中动态指定返回的List<>
的类型,或如何转换,例如List<object>
到{{1}在返回之前。
答案 0 :(得分:7)
您可以拥有一个所有数据类必须实现的接口
public interface IData
{
void FillFromReader(IDataReader dr);
}
然后像这样改变你的方法
public List<T> GetList<T>(string sqlText)
where T : IData, new()
{
List<T> myList = new List<T>();
using (Connection cnx = new Connection(connString))
using (Command cmd = new Command(sqlText, cnx)) {
cnx.Open();
using (IDataReader dr = cmd.ExecuteReader()) {
while (dr.Read())
{
T item = new T();
item.FillFromReader(dr);
myList.Add(item);
}
}
}
return myList();
}
所以基本上每个班级都要负责填写自己的领域。
泛型类型参数的约束where T : IData, new()
至关重要。它告诉方法,T
必须实现接口IData
。这对于能够在不进行强制转换的情况下调用方法FillFromReader
是必要的。数据类必须具有默认构造函数(由new()
指定。这使您可以使用new T()
实例化一个。
我使用带有using
语句的连接,命令和数据读取器包围了代码。 using
语句在块结束时自动关闭并释放资源,并确保发生这种情况,即使应该抛出异常或者语句块应该过早地保留,例如返回语句。
答案 1 :(得分:1)
Olivier的实施很好。它使用泛型和接口为每个实体提供它自己的FillFromDataReader()实现。
你可以把它带得更远。通过使用约定,所有数据水合代码都可以集中和抽象出来。
我将假设您的类属性名称和列名称相同。如果不是,则可以扩展以下代码以将别名属性添加到属性名称。有时属性是根据对象中的其他值计算的,此属性不能被水合。可以在下面的类中创建和实现Ignore属性。
public class DataAccess
{
/// <summary>
/// Hydrates the collection of the type passes in.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql">The SQL.</param>
/// <param name="connection">The connection.</param>
/// <returns>List{``0}.</returns>
public List<T> List<T>(string sql, string connection) where T: new()
{
List<T> items = new List<T>();
using (SqlCommand command = new SqlCommand(sql, new SqlConnection(connection)))
{
string[] columns = GetColumnsNames<T>();
var reader = command.ExecuteReader(CommandBehavior.CloseConnection);
while (reader.Read())
{
T item = new T();
foreach (var column in columns)
{
object val = reader.GetValue(reader.GetOrdinal(column));
SetValue(item, val, column);
}
items.Add(item);
}
command.Connection.Close();
}
return items;
}
/// <summary>
/// Sets the value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="item">The item.</param>
/// <param name="value">The value.</param>
/// <param name="column">The column.</param>
private void SetValue<T>(T item, object value, string column)
{
var property = item.GetType().GetProperty(column);
property.SetValue(item, value, null);
}
/// <summary>
/// Gets the columns names.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>System.String[][].</returns>
private string[] GetColumnsNames<T>() where T : new()
{
T item = new T();
return (from i in item.GetType().GetProperties()
select i.Name).ToArray();
}
}
上面的代码中有几点需要注意。 DBNulls和Nullable类型是特殊情况,需要自定义代码来处理它们。我通常将DBNull转换为null。我从来没有遇到过需要区分两者之间差异的情况。对于Nullalbe类型,只需检测Nullable类型并相应地处理代码。
ORM将消除处理数据访问的许多麻烦。缺点是,您很多时候都与DTO和数据库架构相关联。当然,使用抽象可以包含这个问题。许多公司仍然使用严格存储的过程,大多数ORM在被迫使用存储过程时都会崩溃。它们不适用于存储过程。
我写了一个名为“Hypersonic”的数据访问框架。它位于GitHub上,专门用于存储过程。上面的代码是它的轻量级实现。