web-dev-qa-db-ja.com

データリーダーの行を型付きの結果に変換する

データリーダーを返すサードパーティのライブラリを使用しています。私はそれをオブジェクトのリストに変換する簡単な方法とできるだけ一般的な方法を望みます。
たとえば、2つのプロパティEmployeeIdとNameを持つクラス 'Employee'がある場合、データリーダー(従業員のリストを含む)をList <Employee>に変換したいとします。
私は、データリーダーの行を繰り返し処理し、それぞれの行をEmployeeオブジェクトに変換してリストに追加する以外に選択肢がないと思います。より良い解決策はありますか?私はC#3.5を使用しています。理想的には、あらゆるクラスで機能するようにできるだけ汎用的にしたいと考えています(DataReaderのフィールド名はさまざまなオブジェクトのプロパティ名と一致しています)。

27
Anthony

本当にリストが必要ですか、それとも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()を呼び出すことができます。ジェネリック+デリゲートを使用して、このパターンのコードをより再利用しやすくすることもできると思います。

pdate:今日もこれを見て、一般的なコードを書く気になりました:

_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);
_
61
Joel Coehoorn

次のような拡張メソッドを作成できます。

_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();
_
24
Mehrdad Afshari

これは量産コードにはお勧めしませんが、リフレクションとジェネリックを使用してこれを自動的に行うことができます。

_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
Dan Diplo

私たちは次のソリューションを実装し、それがかなりうまく機能していると感じています。これは非常に単純で、マッパーが行うことよりも少し多くの配線が必要です。ただし、手動で制御できるのは良いことであり、正直なところ、一度接続すれば完了です。

一言で言えば:私たちのドメインモデルは、IDataReaderを受け取り、そこからモデルプロパティを設定するメソッドを持つインターフェースを実装しています。次に、GenericsとReflectionを使用してモデルのインスタンスを作成し、そのモデルでParseメソッドを呼び出します。

コンストラクタを使用してIDataReaderを渡すことを検討しましたが、私たちが行ったbasicパフォーマンスチェックは、インターフェイスが一貫して高速であることを示唆しているようです(少しだけ)。また、インターフェイスルートはコンパイルエラーを介して即座にフィードバックを提供します。

私が好きなことの1つは、あなたがprivate set以下の例のAgeのようなプロパティに対して、データベースから直接設定します。

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;
                    }
                }
            }
        }
    }
}

それを呼び出すには、単純にtypeパラメーターを指定します

IEnumerable<Foo> foos = DataLoader.GetRecords<Foo>(/* params */)
1
Airn5475

注:これは.NET Coreコードです

外部依存関係を気にしないでください(驚くべき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>());
    }
}
1
pim

.NET Core 2.0の場合:

次に、.NET CORE 2.0と連携してRAW SQLを実行し、結果を任意のタイプのリストにマッピングする拡張メソッドを示します。

使用法:

 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;

                }
            }
        }
    }
0
Tony Bourdeaux

最も簡単なソリューション:

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

次に、それらを選択して、任意のタイプにマップします。

0
Mohsen