Dapperでのマルチマッピングの正しい使用


111

dapperのマルチマッピング機能を使用して、ProductItemsと関連する顧客のリストを返そうとしています。

[Table("Product")]
public class ProductItem
{
    public decimal ProductID { get; set; }        
    public string ProductName { get; set; }
    public string AccountOpened { get; set; }
    public Customer Customer { get; set; }
} 

public class Customer
{
    public decimal CustomerId { get; set; }
    public string CustomerName { get; set; }
}

私のダッパーコードは次のとおりです

var sql = @"select * from Product p 
            inner join Customer c on p.CustomerId = c.CustomerId 
            order by p.ProductName";

var data = con.Query<ProductItem, Customer, ProductItem>(
    sql,
    (productItem, customer) => {
        productItem.Customer = customer;
        return productItem;
    },
    splitOn: "CustomerId,CustomerName"
);

これは正常に機能しますが、すべての顧客のプロパティを返すには、splitOnパラメータに完全な列リストを追加する必要があるようです。「CustomerName」を追加しないと、nullが返されます。マルチマッピング機能のコア機能を誤解していますか?毎回列名の完全なリストを追加する必要はありません。


実際に両方のテーブルをdatagridviewに実際に表示する方法は?小さな例は大歓迎です。
Ankur Soni 2016

回答:


184

私はうまく機能するテストを実行しました:

var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(1 as decimal) CustomerId, 'name' CustomerName";

var item = connection.Query<ProductItem, Customer, ProductItem>(sql,
    (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First();

item.Customer.CustomerId.IsEqualTo(1);

splitOnパラメータを分割ポイントとして指定する必要があります。デフォルトはIdです。複数の分割ポイントがある場合は、それらをカンマ区切りのリストに追加する必要があります。

レコードセットが次のようになっているとします。

ProductID | ProductName | AccountOpened | CustomerId | 顧客名
--------------------------------------- ----------- --------------

Dapperは、この順序で列を2つのオブジェクトに分割する方法を知る必要があります。ざっくりとした外観は、顧客が列から始まることを示していますCustomerIdsplitOn: CustomerId

基になるテーブルの列の順序が何らかの理由で反転している場合は、ここで大きな注意が必要です。

ProductID | ProductName | AccountOpened | CustomerName | 顧客ID  
--------------------------------------- ----------- --------------

splitOn: CustomerId 顧客名はnullになります。

CustomerId,CustomerName分割点として指定した場合、dapperは結果セットを3つのオブジェクトに分割しようとしていると想定します。最初は最初から始まり、2番目はから始まりCustomerId、3 番目はから始まりCustomerNameます。


2
サムありがとう ええ、それはCustomerNameの問題であった列の返品注文です| 返されたCustomerId CustomerNameはnullに戻りました。
Richard Forrest

18
覚えておくべき一つのことは、あなたがスペースを持つことができないでspliton、すなわちCustomerId,CustomerNameないCustomerId, CustomerNameDapperのがないので、Trim文字列分割の結果を。単に一般的なsplitonエラーをスローします。ある日私を狂わせた。
jes 2013

2
@vaheedsは常に列名を使用し、星印は使用しないでください。これにより、SQLの処理が減り、この場合のように列の順序が間違っている状況が発生しなくなります。
ハラグ

3
@vaheeds-id、Id、IDに関して、大文字と小文字は区別されません。また、splitOnのテキストをトリミングします-これは、dapperのv1.50.2.0です。
Harag

2
疑問に思う方のために、クエリを3つのオブジェクトに分割する必要がある場合は、「Id」という名前の1つの列と「somethingId」という名前の1つの列で、split句に最初の「Id」を含めてください。Dapperはデフォルトで「Id」で分割しますが、この場合は明示的に設定する必要があります。
Sbu 2017年

27

私たちのテーブルはあなたのものと同じように名前が付けられており、「CustomerID」のようなものが 'select *'操作を使用して2回返される可能性があります。したがって、Dapperはその仕事をしていますが、列が次のようになるため、分割が早すぎる(おそらく)だけです。

(select * might return):
ProductID,
ProductName,
CustomerID, --first CustomerID
AccountOpened,
CustomerID, --second CustomerID,
CustomerName.

これにより、spliton:パラメーターはそれほど役に立ちません。特に、列が返される順序がわからない場合は特にそうです。もちろん、列を手動で指定することもできますが、2017年なので、基本的なオブジェクトの取得でそれを行うことはほとんどありません。

私たちがしていること、そして何千年ものクエリに対して何年もの間うまく機能しているのは、単にIdのエイリアスを使用し、(Dapperのデフォルトの 'Id'を使用して)splitonを指定しないことです。

select 
p.*,

c.CustomerID AS Id,
c.*

...出来上がり!DapperはデフォルトでIdでのみ分割され、そのIdはすべてのCustomer列の前に発生します。もちろん、返される結果セットに追加の列が追加されますが、これは、どの列がどのオブジェクトに属しているかを正確に把握するための追加ユーティリティのオーバーヘッドを非常に最小限に抑えます。そして、これは簡単に拡張できます。住所と国の情報が必要ですか?

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*

何よりも、どの列がどのオブジェクトに関連付けられているかを最小量のSQLで明確に示しています。残りはDapperが行います。


テーブルにIDフィールドがない限り、これは簡潔なアプローチです。
バーナードヴァンダー

このアプローチを使用しても、テーブルにIdフィールドを含めることができますが、それはPKである必要があります。エイリアスを作成する必要はないので、実際には少し手間がかかりません。(PKではない「Id」という列があるのは非常に珍しい(悪い形式ですか?)と思います。)
BlackjacketMack

5

次の構造を想定します。は分割のポイントであり、Tsはマッピングが適用されるエンティティです。

       TFirst         TSecond         TThird           TFourth
------------------+-------------+-------------------+------------
col_1 col_2 col_3 | col_n col_m | col_A col_B col_C | col_9 col_8
------------------+-------------+-------------------+------------

以下は、作成する必要があるdapperクエリです。

Query<TFirst, TSecond, TThird, TFourth, TResut> (
    sql : query,
    map: Func<TFirst, TSecond, TThird, TFourth, TResut> func,
    parma: optional,
    splitOn: "col_3, col_n, col_A, col_9")

したがって、TFirstがcol_1 col_2 col_3をマップし、TSecondがcol_n col_m ...

splitOn式は次のように変換されます。

「col_3」という名前またはエイリアスの列が見つかり、マッピング結果に「col_3」も含まれるまで、すべての列のTFristへのマッピングを開始します。

次に、TSecondへのマッピングを開始し、「col_n」から始まるすべての列をマッピングして、新しいセパレータが見つかるまでマッピングを続行します。

SQLクエリの列とマッピングオブジェクトの小道具は1:1の関係にあります(つまり、同じ名前にする必要があります)。SQLクエリの結果の列名が異なる場合は、 'AS [ Some_Alias_Name] '式。


2

もう1つ注意点があります。CustomerIdフィールドがnullの場合(通常、左結合を使用するクエリ)、DapperはCustomer = nullでProductItemを作成します。上記の例では:

var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(null as decimal) CustomerId, 'n' CustomerName";
var item = connection.Query<ProductItem, Customer, ProductItem>(sql, (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First();
Debug.Assert(item.Customer == null); 

そして、さらに1つの警告/トラップ。splitOnで指定されたフィールドをマップせず、そのフィールドにnullが含まれている場合、Dapperは関連オブジェクト(この場合はCustomer)を作成して入力します。このクラスを以前のSQLで使用する方法を示すには、次のようにします。

public class Customer
{
    //public decimal CustomerId { get; set; }
    public string CustomerName { get; set; }
}
...
Debug.Assert(item.Customer != null);
Debug.Assert(item.Customer.CustomerName == "n");  

クラスにCustomeridを追加する以外に2番目の例の解決策はありますか?nullオブジェクトが必要なのですが、空のオブジェクトが表示されます。(stackoverflow.com/questions/27231637/...
jmzagorski

1

私は一般的に私のリポジトリでこれを行い、私のユースケースに適しています。共有したいと思いました。多分誰かがこれをさらに拡張するでしょう。

いくつかの欠点は次のとおりです。

  • これは、外部キープロパティが子オブジェクトの名前+ "Id"、たとえばUnitIdであることを前提としています。
  • 1つの子オブジェクトを親にマッピングするだけです。

コード:

    public IEnumerable<TParent> GetParentChild<TParent, TChild>()
    {
        var sql = string.Format(@"select * from {0} p 
        inner join {1} c on p.{1}Id = c.Id", 
        typeof(TParent).Name, typeof(TChild).Name);

        Debug.WriteLine(sql);

        var data = _con.Query<TParent, TChild, TParent>(
            sql,
            (p, c) =>
            {
                p.GetType().GetProperty(typeof (TChild).Name).SetValue(p, c);
                return p;
            },
            splitOn: typeof(TChild).Name + "Id");

        return data;
    }

0

大きなエンティティをマップする必要がある場合、各フィールドを書き込むのは困難な作業でなければなりません。

@BlackjacketMackの回答を試しましたが、テーブルの1つにId列があり、他のテーブルにはありません(これはDB設計の問題であることはわかっていますが...)、これによりdapperに余分なスプリットが挿入されます。

select
p.*,

c.CustomerID AS Id,
c.*,

address.AddressID AS Id,
address.*,

country.CountryID AS Id,
country.*

うまくいきません。その後、私はこれまでほとんど変化して終了し、ちょうど、では変更の場合があり、テーブル上の任意のフィールドと一致しない名前のスプリットポイントを挿入as Idすることによりas _SplitPoint_、このような、最後のSQLスクリプトのルックスを:

select
p.*,

c.CustomerID AS _SplitPoint_,
c.*,

address.AddressID AS _SplitPoint_,
address.*,

country.CountryID AS _SplitPoint_,
country.*

次にdapperで、splitOnを1つだけ追加します

cmd =
    "SELECT Materials.*, " +
    "   Product.ItemtId as _SplitPoint_," +
    "   Product.*, " +
    "   MeasureUnit.IntIdUM as _SplitPoint_, " +
    "   MeasureUnit.* " +
    "FROM   Materials INNER JOIN " +
    "   Product ON Materials.ItemtId = Product.ItemtId INNER JOIN " +
    "   MeasureUnit ON Materials.IntIdUM = MeasureUnit.IntIdUM " +
List < Materials> fTecnica3 = (await dpCx.QueryAsync<Materials>(
        cmd,
        new[] { typeof(Materials), typeof(Product), typeof(MeasureUnit) },
        (objects) =>
        {
            Materials mat = (Materials)objects[0];
            mat.Product = (Product)objects[1];
            mat.MeasureUnit = (MeasureUnit)objects[2];
            return mat;
        },
        splitOn: "_SplitPoint_"
    )).ToList();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.