2011-09-19 8 views
75

Próbuję użyć funkcji Multimapping z dapper, aby zwrócić listę ProductItems i powiązanych klientów.Prawidłowe użycie Multimapping w Dapper

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

Mój kod elegancki jest następujący

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" 
); 

Działa to dobrze, ale wydaje mi się, aby dodać pełną listę kolumn do parametru splitOn aby powrócić wszystkie właściwości klientów. Jeśli nie dodaję "CustomerName", zwróci ona wartość null. Brakuje mi zrozumienia podstawowej funkcjonalności funkcji multimapping. Nie chcę za każdym razem dodawać pełnej listy nazw kolumn.

+0

jak właściwie wyświetlić obie tabele w datagridview? mały przykład będzie bardzo doceniany. –

Odpowiedz

124

Właśnie przeprowadziła test, który działa prawidłowo:

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 param musi być określony jako punktu podziału, to domyślne do Id. Jeśli istnieje wiele punktów podziału, musisz dodać je na liście rozdzielanej przecinkami.

Wypowiedz rekordów wygląda następująco:

 
ProductID | ProductName | AccountOpened | CustomerId | CustomerName 
--------------------------------------- ------------------------- 

Wytworny musi wiedzieć, jak podzielić kolumny w tej kolejności na 2 przedmiotów. Pobieżny wygląd pokazuje, że klient zaczyna od kolumny CustomerId, czyli splitOn: CustomerId.

Jest duży zastrzeżenie tutaj, jeśli zamawianie kolumny w tabeli bazowej jest przerzucony z jakiegoś powodu:

 
ProductID | ProductName | AccountOpened | CustomerName | CustomerId 
--------------------------------------- ------------------------- 

splitOn: CustomerId spowoduje null nazwy klienta.

Jeśli określisz CustomerId,CustomerName jako punkty podziału, to dapper zakłada, że ​​próbujesz podzielić zestaw wyników na 3 obiekty. Pierwsze zaczyna się na początku, drugie zaczyna się od CustomerId, trzecie pod numerem CustomerName.

+0

Dzięki Sam. Tak, twoje prawo, to kolejność zwracania kolumn była problemem z CustomerName | Identyfikator CustomerId zwrócony CustomerName wracał null. –

+6

Jedną z rzeczy do zapamiętania jest to, że nie można mieć spacji w 'spliton', tj.' CustomerId, CustomerName', a nie 'CustomerId, CustomerName', ponieważ Dapper nie" Trim "wyniki podziału na łańcuchy. Po prostu wyrzuci ogólny błąd podziału. Doprowadził mnie do szału pewnego dnia. – jes

+0

co, jeśli chcę uzyskać listę produktów skierowaną do klientów? Jak sobie poradzisz z tym scenariuszem w Dapper.Net? – touseefkhan4pk

2

Jest jeszcze jedno zastrzeżenie. Jeśli pole CustomerId ma wartość null (zazwyczaj w zapytaniach z lewym złączeniem) Dapper tworzy ProductItem z Customer = null. W powyższym przykładzie:

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

I jeszcze jedno zastrzeżenie/pułapka. Jeśli nie zamapujesz pola określonego w splitOn, a pole zawiera wartość pustą, Dapper tworzy i wypełnia powiązany obiekt (w tym przypadku Klient). Aby zademonstrować użycie tej klasy z poprzednim 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"); 
+0

jest rozwiązanie drugiego przykładu oprócz dodania Customerid do klasy? Mam problem, w którym potrzebuję obiektu zerowego, ale daje mi pusty obiekt. (http://stackoverflow.com/questions/27231637/dapper-left-joins-not-returning-null-object-but-an-empty-object) –

0

Robię to ogólnie w moim repo, działa dobrze dla mojego przypadku użycia. Myślałem, że się podzielę. Może ktoś rozszerzy to dalej.

Niektóre wady są:

  • Zakłada zagranicznego kluczowe właściwości są nazwa obiektu podrzędnego + „Id”, na przykład UnitId.
  • Mam tylko odwzorowanie 1 obiektu podrzędnego na rodzica.

Kod:

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

Nasze stoły są nazwane podobnie do Twojego, gdzie coś takiego jak „CustomerID” może być zwrócony dwukrotnie używając „SELECT *” operację. Dlatego Wytworny robi swoją pracę, ale tylko dzielenie zbyt wcześnie (ewentualnie), ponieważ kolumny byłoby:

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

To sprawia, że ​​spliton: parametr nie jest tak przydatny, szczególnie jeśli nie jesteś pewien, co zamówić kolumn są zwracane. Oczywiście możesz ręcznie określić kolumny ... ale jest rok 2017, a my rzadko to robimy, aby uzyskać podstawowy obiekt.

To, co robimy i sprawdzało się w tysiącach zapytań przez wiele lat, to po prostu używać aliasu dla Id i nigdy nie określać spliton (używając domyślnego "ID" Dappera).

select 
p.*, 

c.CustomerID AS Id, 
c.* 

... voila! Dapper domyślnie podzieli się tylko na identyfikator, a identyfikator ten występuje przed wszystkimi kolumnami klienta. Oczywiście doda on dodatkową kolumnę do twojego zestawu wyników, ale jest to bardzo minimalny narzut dla dodanej użyteczności, która dokładnie określa, które kolumny należą do jakiego obiektu. I możesz łatwo to rozwinąć. Potrzebujesz informacji o adresie i kraju?

select 
p.*, 

c.CustomerID AS Id, 
c.*, 

address.AddressID AS Id, 
address.*, 

country.CountryID AS Id, 
country.* 

Najlepszy ze wszystkich, wyraźnie pokazuje się w minialnej ilości sql, które kolumny są związane z którym obiektem. Dapper zajmie się resztą.

+1

To jest piękne. Dzięki! –

Powiązane problemy