13

私はこのようなクラスを持っています:

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の使用だけでした。

プロパティ/クラスに適用して、クエリ行からクラスに自動的にデータを入力できるような属性はありますか?

4

8 に答える 8

16

私は別の答えを提案することにしました。これは実際には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;
}
于 2012-07-27T12:27:07.957 に答える
14

プロパティを自分でマップするか、ORM(オブジェクトリレーショナルマッパー)を使用する必要があります。

MicrosoftはEntityFrameworkを提供していますが、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とLinqtoSQLもこれを実行しますが、LinqクエリをSQLステートメントに変換するなど、追加の処理も実行します。これも役立つ場合があります。

于 2012-07-27T11:13:03.433 に答える
6

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 */ }
}

注意すべきことは、クエリ内のフィールドの順序だけです(クラスで定義されているプロパティの順序と一致する必要があります)。

クラスを作成し、クエリを作成するだけで、「マッピング」が処理されます。

警告私はこのメソッドを頻繁に使用し、問題は発生しませんでしたが、部分クラスでは正しく機能しません。部分的なモデルになると、とにかくORMフレームワークを使用する方がはるかに優れています。

于 2012-07-27T11:30:43.597 に答える
2

私はLinqtoSQLを使用して、次のようにします。

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;
    }
}
于 2012-07-27T11:11:40.003 に答える
1

raw .NET Frameworkには、デフォルトではそのような機能はありません。Entity Frameworkを使用することもできますが、それが適切なソリューションでない場合は、別の方法としてリフレクションメカニズムを使用します。

  1. クラスの各パブリックプロパティの列名を保持できるカスタム属性クラスを作成します。

  2. データベースからレコードを取得した後、Productクラスのオブジェクトをインスタンス化し、プロパティを列挙します。カスタム属性を持つプロパティごとに-カスタム属性で定義された列名に従って値を変更するためSetValueに使用します。PropertyInfo

次の点を考慮してください。

  • 解決策は、単純な割り当てに対して非常にオーバーヘッドがあります。Product多くのテーブルと-のような多くのクラスがあり、それらすべてを自動的に初期化する1つのコードを記述したい場合にのみ意味があります
  • リフレクションはそれ自体がオーバーヘッドであるため、長期的にはある程度のキャッシュが必要になります
于 2012-07-27T11:23:42.740 に答える
1

考えられる解決策の1つ:

SQLクエリでは、 「PATH ModewithFORXML」句を使用できます。

クエリの結果はXMLになり、C#オブジェクトに直接逆シリアル化できます。

また、ネストされた大規模なSQLクエリでも非常にうまく機能します。

于 2013-06-10T08:54:41.567 に答える
0

次に、EntityFrameworkまたはLinqTo SQLを使用する必要があります。それを使用したくない場合は、自分でマップ/入力する必要があります。

EntityFrameworkの詳細 http://msdn.microsoft.com/en-us/data/ef.aspx

LinqtoSQLの詳細 http://msdn.microsoft.com/en-us/library/bb386976.aspx

于 2012-07-27T11:07:29.723 に答える
-1
select 'public ' + case DATA_TYPE  
when 'varchar' then 'string'
when 'nvarchar' then 'string'
when 'DateTime' then 'DateTime'
when 'bigint' then 'long' 
else DATA_TYPE end +' '+ COLUMN_NAME + ' {get; set;}' 
from INFORMATION_SCHEMA.COLUMNS  WHERE TABLE_NAME ='YOUR TABLE NAME'  ORDER BY ORDINAL_POSITION
于 2016-07-29T12:31:39.483 に答える