将数据读取器中的行转换为键入的结果

时间:2009-07-29 20:46:40

标签: c# list datareader

我正在使用第三方库来返回数据阅读器。我想要一个简单的方法,并尽可能通用,将其转换为对象列表 例如,假设我有一个具有2个属性EmployeeId和Name的类'Employee',我希望将数据读取器(包含雇员列表)转换为List<雇员取代。
我想我别无选择,只能迭代数据阅读器的行,并为每个行转换成一个我将添加到List的Employee对象。更好的解决方案?我正在使用C#3.5,理想情况下我希望它尽可能通用,以便它适用于任何类(DataReader中的字段名称与各种对象的属性名称匹配)。

10 个答案:

答案 0 :(得分:61)

你真的需要一个列表,还是IEnumerable足够好?

我知道你希望它是通用的,但更常见的模式是在目标对象类型上有一个接受数据行(或IDataRecord)的静态Factory方法。这看起来像这样:

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }

    public static Employee Create(IDataRecord record)
    {
        return new Employee
        {
           Id = record["id"],
           Name = record["name"]
        };
    }
}

public IEnumerable<Employee> GetEmployees()
{
    using (var reader = YourLibraryFunction())
    {
       while (reader.Read())
       {
           yield return Employee.Create(reader);
       }
    }
}

然后,如果确实需要列表而不是IEnumerable,则可以在结果上调用.ToList()。我想你也可以使用泛型+委托来使这个模式的代码更易于重用。

更新:我今天再次看到这一点,感觉就像编写通用代码一样:

public IEnumerable<T> GetData<T>(IDataReader reader, Func<IDataRecord, T> BuildObject)
{
    try
    {
        while (reader.Read())
        {
            yield return BuildObject(reader);
        }
    }
    finally
    {
         reader.Dispose();
    }
}

//call it like this:
var result = GetData(YourLibraryFunction(), Employee.Create);

答案 1 :(得分:24)

您可以构建一个扩展方法,如:

public static List<T> ReadList<T>(this IDataReader reader, 
                                  Func<IDataRecord, T> generator) {
     var list = new List<T>();
     while (reader.Read())
         list.Add(generator(reader));
     return list;
}

并使用它:

var employeeList = reader.ReadList(x => new Employee {
                                               Name = x.GetString(0),
                                               Age = x.GetInt32(1)
                                        });

乔尔的建议很好。您可以选择退回IEnumerable<T>。转换上面的代码很容易:

public static IEnumerable<T> GetEnumerator<T>(this IDataReader reader, 
                                              Func<IDataRecord, T> generator) {
     while (reader.Read())
         yield return generator(reader);
}

如果要自动将列映射到属性,代码构思是相同的。您可以使用查询generator的函数替换上述代码中的typeof(T)函数,并通过读取匹配列使用反射设置对象的属性。但是,我个人更喜欢定义一个工厂方法(就像Joel的答案中提到的那个)并将它的委托传递给这个函数:

 var list = dataReader.GetEnumerator(Employee.Create).ToList();

答案 2 :(得分:3)

虽然我不建议将其用于生产代码,但您可以使用反射和泛型自动执行此操作:

public static class DataRecordHelper
{
    public static void CreateRecord<T>(IDataRecord record, T myClass)
    {
        PropertyInfo[] propertyInfos = typeof(T).GetProperties();

        for (int i = 0; i < record.FieldCount; i++)
        {
            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                if (propertyInfo.Name == record.GetName(i))
                {
                    propertyInfo.SetValue(myClass, Convert.ChangeType(record.GetValue(i), record.GetFieldType(i)), null);
                    break;
                }
            }
        }
    }
}

public class Employee
{
    public int Id { get; set; }
    public string LastName { get; set; }
    public DateTime? BirthDate { get; set; }

    public static IDataReader GetEmployeesReader()
    {
        SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString);

        conn.Open();
        using (SqlCommand cmd = new SqlCommand("SELECT EmployeeID As Id, LastName, BirthDate FROM Employees"))
        {
            cmd.Connection = conn;
            return cmd.ExecuteReader(CommandBehavior.CloseConnection);
        }
    }

    public static IEnumerable GetEmployees()
    {
        IDataReader rdr = GetEmployeesReader();
        while (rdr.Read())
        {
            Employee emp = new Employee();
            DataRecordHelper.CreateRecord<Employee>(rdr, emp);

            yield return emp;
        }
    }
}

然后,您可以使用CreateRecord<T>()从数据读取器的字段中实例化任何类。

<asp:GridView ID="GvEmps" runat="server" AutoGenerateColumns="true"></asp:GridView>

GvEmps.DataSource = Employee.GetEmployees();
GvEmps.DataBind();

答案 3 :(得分:1)

我们已经实施了以下解决方案,并感觉它运作良好。它非常简单,需要比映射器更多的连线。但是,有时手动控制很好,老实说,你连接一次就完成了。

简而言之:我们的域模型实现了一个接口,该接口有一个接收IDataReader的方法并从中填充模型属性。然后,我们使用Generics和Reflection创建模型的实例,并在其上调用Parse方法。

我们考虑使用构造函数并将IDataReader传递给它,但我们所做的基本性能检查似乎表明界面始终更快(如果只是一点点)。此外,接口路由通过编译错误提供即时反馈。

我喜欢的一件事是,您可以在下面的示例中使用private setAge之类的属性,并直接从数据库中设置它们。

public interface IDataReaderParser
{
    void Parse(IDataReader reader);
}

public class Foo : IDataReaderParser
{
    public string Name { get; set; }
    public int Age { get; private set; }

    public void Parse(IDataReader reader)
    {
        Name = reader["Name"] as string;
        Age = Convert.ToInt32(reader["Age"]);
    }
}

public class DataLoader
{
    public static IEnumerable<TEntity> GetRecords<TEntity>(string connectionStringName, string storedProcedureName, IEnumerable<SqlParameter> parameters = null)
                where TEntity : IDataReaderParser, new()
    {
        using (var sqlCommand = new SqlCommand(storedProcedureName, Connections.GetSqlConnection(connectionStringName)))
        {
            using (sqlCommand.Connection)
            {
                sqlCommand.CommandType = CommandType.StoredProcedure;
                AssignParameters(parameters, sqlCommand);
                sqlCommand.Connection.Open();

                using (var sqlDataReader = sqlCommand.ExecuteReader())
                {
                    while (sqlDataReader.Read())
                    {
                        //Create an instance and parse the reader to set the properties
                        var entity = new TEntity();
                        entity.Parse(sqlDataReader);
                        yield return entity;
                    }
                }
            }
        }
    }
}

要调用它,只需提供类型参数

即可
IEnumerable<Foo> foos = DataLoader.GetRecords<Foo>(/* params */)

答案 4 :(得分:1)

  

注意:这是.NET核心代码

一个愚蠢的高性能选项,如果你不介意外部依赖(令人惊讶的Fast Member nuget包):

public static T ConvertToObject<T>(this SqlDataReader rd) where T : class, new()
{

    Type type = typeof(T);
    var accessor = TypeAccessor.Create(type);
    var members = accessor.GetMembers();
    var t = new T();

    for (int i = 0; i < rd.FieldCount; i++)
    {
        if (!rd.IsDBNull(i))
        {
            string fieldName = rd.GetName(i);

            if (members.Any(m => string.Equals(m.Name, fieldName, StringComparison.OrdinalIgnoreCase)))
            {
                accessor[t, fieldName] = rd.GetValue(i);
            }
        }
    }

    return t;
}

使用:

public IEnumerable<T> GetResults<T>(SqlDataReader dr) where T : class, new()
{
    while (dr.Read())
    {
        yield return dr.ConvertToObject<T>());
    }
}

答案 5 :(得分:0)

最简单的解决方案:

var dt=new DataTable();
dt.Load(myDataReader);
list<DataRow> dr=dt.AsEnumerable().ToList();

然后选择它们以将它们映射到任何类型。

答案 6 :(得分:0)

对于.NET Core 2.0:

这是一个扩展方法,它与.NET CORE 2.0一起使用来执行RAW SQL并将结果映射到任意类型的LIST:

用法:

 var theViewModel = new List();
 string theQuery = @"SELECT * FROM dbo.Something";
 theViewModel = DataSQLHelper.ExecSQL(theQuery,_context);

 using Microsoft.EntityFrameworkCore;
 using System.Data;
 using System.Data.SqlClient;
 using System.Reflection;

public static List ExecSQL(string query, myDBcontext context)
 {
 using (context)
 {
 using (var command = context.Database.GetDbConnection().CreateCommand())
 {
 command.CommandText = query;
 command.CommandType = CommandType.Text;
 context.Database.OpenConnection();
                using (var result = command.ExecuteReader())
                {
                    List<T> list = new List<T>();
                    T obj = default(T);
                    while (result.Read())
                    {
                        obj = Activator.CreateInstance<T>();
                        foreach (PropertyInfo prop in obj.GetType().GetProperties())
                        {
                            if (!object.Equals(result[prop.Name], DBNull.Value))
                            {
                                prop.SetValue(obj, result[prop.Name], null);
                            }
                        }
                        list.Add(obj);
                    }
                    return list;

                }
            }
        }
    }

答案 7 :(得分:0)

喜欢魔术

我个人讨厌在构造函数中进行手动映射,我也不喜欢自己进行反射。因此,这是另一个出色的(相当普遍的)Newtonsoft JSON库的解决方案。

仅当您的属性名称与datareader列名称完全匹配时,它才有效,但对我们来说效果很好。

...假设您的数据读取器名称为“ yourDataReader” ...

        var dt = new DataTable();
        dt.Load(yourDataReader);
        // creates a json array of objects
        string json = Newtonsoft.Json.JsonConvert.SerializeObject(dt);
        // this is what you're looking for right??
        List<YourEntityType> list = 
Newtonsoft.Json.JsonConvert
.DeserializeObject<List<YourEntityType>>(json);

答案 8 :(得分:0)

我找到了解决方案。

var cmd = ctx.Connection.CreateCommand();

T result = DbDataReaderdHelper.Fill<T>(cmd)

public static class DbDataReaderdHelper
{
    public static List<T> Fill<T>(DbCommand dbCommand) where T : new()
    {
        List<T> result = new List<T>();
        var reader = dbCommand.ExecuteReader();

        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Type type = typeof(T);
                T obj = (T)Activator.CreateInstance(type);
                PropertyInfo[] properties = type.GetProperties();

                foreach (PropertyInfo property in properties)
                {
                    var value = reader[property.Name];

                    try
                    {
                        if (value != null)
                        {
                            var convertedValue = TypeDescriptor.GetConverter(property.PropertyType).ConvertFromInvariantString(value.ToString());

                            property.SetValue(obj, convertedValue);
                        }
                    }
                    catch {}
                }
                result.Add(obj);
            }
        }

        reader.Close();

        return result;
    }
}

答案 9 :(得分:0)

我的版本

用法:

var Q = await Reader.GetTable<DbRoom>("SELECT id, name FROM rooms");

PgRoom是

public class DbRoom
{
    [Column("id")]
    public int Id { get; set; }

    [Column("name")]
    public string Name { get; set; }
}

Reader.GetTable包含:

            using (var R = await Q.ExecuteReaderAsync())
            {
                List<T> Result = new List<T>();

                Dictionary<int, PropertyInfo> Props = new Dictionary<int, PropertyInfo>();

                foreach (var p in typeof(T).GetProperties())
                {
                    for (int i = 0; i < R.FieldCount; i++)
                    {
                        if (p.GetCustomAttributes<ColumnAttribute>().FirstOrDefault(t => t.Name == R.GetName(i)) != null
                            && p.PropertyType == R.GetFieldType(i))
                        {
                            Props.Add(i, p);
                        }
                    }
                }

                while (await R.ReadAsync())
                {
                    T row = new T();

                    foreach (var kvp in Props)
                    {
                        kvp.Value.SetValue(row, R[kvp.Key]);
                    }

                    Result.Add(row);
                }

                return Result;
            }