私はこのようなクラスを持っています:
public class Product
{
public int ProductId { get; private set; }
public int SupplierId { get; private set; }
public string Name { get; private set; }
public decimal Price { get; private set; }
public int Stock { get; private set; }
public int PendingStock { get; private set; }
}
これらの詳細は、次のようにデータベースから取得できます。
SELECT product_id, supplier_id, name, price, total_stock, pending_stock
FROM products
WHERE product_id = ?
値を設定するためにDataSet
またはDataTable
を手動で実行する必要はありません。
ある種のバインディング/マッピングメカニズムを使用してクラスにデータを設定する方法があると確信していますが、私が見つけたのは、winformsコンポーネントへのバインディングまたはXAMLの使用だけでした。
プロパティ/クラスに適用して、クエリ行からクラスに自動的にデータを入力できるような属性はありますか?
私は別の回答を提案することにしました。これは実際にはAlexによって提供された回答の拡張です(つまり、すべてのクレジットが彼にあります)が、列名2プロパティ名のマッピングのために属性が導入されています。
まず、列名を保持するためのカスタム属性が必要です。
[AttributeUsage(AttributeTargets.Property, Inherited = true)]
[Serializable]
public class MappingAttribute : Attribute
{
public string ColumnName = null;
}
この属性は、データベース行から入力されるクラスのプロパティに適用する必要があります。
public class Product
{
[Mapping(ColumnName = "product_id")]
public int ProductId { get; private set; }
[Mapping(ColumnName = "supplier_id")]
public int SupplierId { get; private set; }
[Mapping(ColumnName = "name")]
public string Name { get; private set; }
[Mapping(ColumnName = "price")]
public decimal Price { get; private set; }
[Mapping(ColumnName = "total_stock")]
public int Stock { get; private set; }
[Mapping(ColumnName = "pending_stock")]
public int PendingStock { get; private set; }
}
そして、属性が列名を取得するために使用されることを除いて、残りはAlexが提案したとおりに進みます。
T MapToClass<T>(SqlDataReader reader) where T : class
{
T returnedObject = Activator.CreateInstance<T>();
PropertyInfo[] modelProperties = returnedObject.GetType().GetProperties();
for (int i = 0; i < modelProperties.Length; i++)
{
MappingAttribute[] attributes = modelProperties[i].GetCustomAttributes<MappingAttribute>(true).ToArray();
if (attributes.Length > 0 && attributes[0].ColumnName != null)
modelProperties[i].SetValue(returnedObject, Convert.ChangeType(reader[attributes[0].ColumnName], modelProperties[i].PropertyType), null);
}
return returnedObject;
}
プロパティを自分でマップするか、ORM(オブジェクトリレーショナルマッパー)を使用する必要があります。
Microsoftは Entity Framework を提供していますが、 Dapper はオーバーヘッドが少なくて済み、要件によっては実行可能なオプションになる場合があります。
あなたの場合、Dapperコードは次のようになります。
var query = @"SELECT product_id, supplier_id, name, price, total_stock, pending_stock
FROM products
WHERE product_id = @id";
var product = connection.Query<Product>(query, new { id = 23 });
完全を期すために、ここでDapperについて話していることを指摘することが重要です。これは、SQLの結果をオブジェクトにマッピングすることに関する質問だからです。 EFとLinqto SQLもこれを実行しますが、LinqクエリをSQLステートメントに変換するなど、追加の処理も実行します。これも役立つ場合があります。
ORMフレームワーク(Entity Frameworkなど)を活用したくない場合は、手動で行うことができます。
T MapToClass<T>(SqlDataReader reader) where T : class
{
T returnedObject = Activator.CreateInstance<T>();
List<PropertyInfo> modelProperties = returnedObject.GetType().GetProperties().OrderBy(p => p.MetadataToken).ToList();
for (int i = 0; i < modelProperties.Count; i++)
modelProperties[i].SetValue(returnedObject, Convert.ChangeType(reader.GetValue(i), modelProperties[i].PropertyType), null);
return returnedObject;
}
あなたはそれをこのように使います:
Product P = new Product(); // as per your example
using(SqlDataReader reader = ...)
{
while(reader.Read()) { P = MapToClass<Product(reader); /* then you use P */ }
}
注意すべきことは、クエリ内のフィールドの順序だけです(クラスで定義されているプロパティの順序と一致する必要があります)。
あなたがする必要があるのは、クラスを構築し、クエリを書くことだけです、そしてそれは「マッピング」の面倒を見ます。
[〜#〜] warning [〜#〜]私はこのメソッドを頻繁に使用し、問題は発生しませんでしたが、部分クラスでは正しく機能しません。部分的なモデルになると、とにかくORMフレームワークを使用する方がはるかに良いでしょう。
Linq to SQLを使用して、次のようにします。
public class Product
{
public int ProductId { get; private set; }
public int SupplierId { get; private set; }
public string Name { get; private set; }
public decimal Price { get; private set; }
public int Stock { get; private set; }
public int PendingStock { get; private set; }
public Product(int id)
{
using(var db = new MainContext())
{
var q = (from c in product where c.ProductID = id select c).SingleOrDefault();
if(q!=null)
LoadByRec(q);
}
}
public Product(product rec)
{
LoadByRec(q);
}
public void LoadByRec(product rec)
{
ProductId = rec.product_id;
SupplierID = rec.supplier_id;
Name = rec.name;
Price = rec.price;
Stock = rec.total_stock;
PendingStock = rec.pending_stock;
}
}
Raw .NET Frameworkには、デフォルトではそのような機能はありません。 Entity Frameworkを使用することもできますが、それが適切なソリューションでない場合は、代わりにリフレクションメカニズムを使用します。
クラスの各パブリックプロパティの列名を保持できるカスタム属性クラスを作成します。
データベースからレコードを取得した後、Product
クラスのオブジェクトをインスタンス化し、プロパティを列挙します。カスタム属性を持つプロパティごとに、SetValue
のPropertyInfo
を使用して、カスタム属性で定義された列名に従って値を変更します。
次の点を考慮してください。
Product
のような多くのテーブルと多くのクラスがあり、それらすべてを自動的に初期化する1つのコードを記述したい場合にのみ意味があります。1つの可能な解決策:
SQLクエリでは、 "PATH Mode with FOR XML" 句を使用できます。
クエリの結果はXMLになります。これは C#オブジェクトに直接逆シリアル化 できます。
また、ネストされた大規模なSQLクエリでも非常にうまく機能します。
次に、EntityFrameworkまたはLinqTo SQLを使用する必要があります。それを使用したくない場合は、自分でマップ/入力する必要があります。
entity Frameworkの詳細 http://msdn.Microsoft.com/en-us/data/ef.aspx
linq to SQLの詳細 http://msdn.Microsoft.com/en-us/library/bb386976.aspx