Dapper.NET
Multimapping
Szukaj…
Składnia
-
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>( this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
-
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
-
public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
Parametry
Parametr | Detale |
---|---|
cnn | Twoje połączenie z bazą danych, które musi być już otwarte. |
sql | Polecenie do wykonania. |
typy | Tablica typów w zestawie rekordów. |
mapa | Func<> który obsługuje konstrukcję wyniku zwrotu. |
param | Obiekt do wyodrębnienia parametrów. |
transakcja | Transakcja, której częścią jest to zapytanie, jeśli istnieje. |
buforowane | Określa, czy buforować odczyt wyników zapytania. Jest to parametr opcjonalny z domyślną wartością true. Gdy buforowane jest true, wyniki są buforowane na List<T> a następnie zwracane jako IEnumerable<T> który jest bezpieczny dla wielokrotnego wyliczania. Gdy buforowane jest false, połączenie sql jest utrzymywane otwarte do czasu zakończenia odczytu, co pozwala na przetworzenie pojedynczego wiersza w pamięci. Wiele wyliczeń spowoduje odrodzenie dodatkowych połączeń z bazą danych. Chociaż buforowane fałsz jest bardzo wydajne w zmniejszaniu zużycia pamięci, jeśli zachowuje się tylko bardzo małe fragmenty zwracanych rekordów, ma znaczny narzut wydajności w porównaniu do chętnego materializowania zestawu wyników. Wreszcie, jeśli masz wiele równoległych niebuforowanych połączeń sql, musisz rozważyć głodzenie puli połączeń, powodując żądania blokowania, aż połączenia będą dostępne. |
podzielone na | Pole, z którego powinniśmy podzielić i odczytać drugi obiekt (domyślnie: id). Może to być lista rozdzielana przecinkami, gdy w rekordzie znajduje się więcej niż 1 typ. |
CommandTimeout | Liczba sekund przed upływem limitu czasu wykonywania polecenia. |
CommandType | Czy to jest przechowywany proc czy partia? |
Proste mapowanie wielu tabel
Powiedzmy, że mamy zapytanie o pozostałych jeźdźców, którzy muszą zapełnić klasę Person.
Nazwa | Urodzony | Rezydencja |
---|---|---|
Daniel Dennett | 1942 r | Stany Zjednoczone Ameryki |
Sam Harris | 1967 | Stany Zjednoczone Ameryki |
Richard dawkins | 1941 | Zjednoczone Królestwo |
public class Person
{
public string Name { get; set; }
public int Born { get; set; }
public Country Residience { get; set; }
}
public class Country
{
public string Residence { get; set; }
}
Możemy wypełnić klasę osoby, a także właściwość Rezydencji wystąpieniem Country, używając Query<>
przeciążeniowego Query<>
które pobiera Func<>
którego można użyć do utworzenia zwróconej instancji. Func<>
może przyjmować do 7 typów danych wejściowych, przy czym ostateczny ogólny argument zawsze jest typem zwracanym.
var sql = @"SELECT 'Daniel Dennett' AS Name, 1942 AS Born, 'United States of America' AS Residence
UNION ALL SELECT 'Sam Harris' AS Name, 1967 AS Born, 'United States of America' AS Residence
UNION ALL SELECT 'Richard Dawkins' AS Name, 1941 AS Born, 'United Kingdom' AS Residence";
var result = connection.Query<Person, Country, Person>(sql, (person, country) => {
if(country == null)
{
country = new Country { Residence = "" };
}
person.Residience = country;
return person;
},
splitOn: "Residence");
Zwróć uwagę na użycie argumentu
splitOn: "Residence"
który jest pierwszą kolumną następnego typu klasy, który należy wypełnić (w tym przypadkuCountry
). Dapper automatycznie wyszuka kolumnę o nazwie Id do podziału, ale jeśli jej nie znajdzie, asplitOn
nie zostanie udostępnionySystem.ArgumentException
zostanie wygenerowany z pomocnym komunikatem. Więc chociaż jest to opcjonalne, zwykle będziesz musiał podać wartośćsplitOn
.
Mapowanie jeden do wielu
Spójrzmy na bardziej złożony przykład, który zawiera relację jeden do wielu. Nasze zapytanie będzie teraz zawierać wiele wierszy zawierających zduplikowane dane i będziemy musieli sobie z tym poradzić. Robimy to z odnośnikiem w zamknięciu.
Zapytanie zmienia się nieznacznie, podobnie jak przykładowe klasy.
ID | Nazwa | Urodzony | CountryId | Nazwa kraju | BookId | BookName |
---|---|---|---|---|---|---|
1 | Daniel Dennett | 1942 r | 1 | Stany Zjednoczone Ameryki | 1 | Burze mózgów |
1 | Daniel Dennett | 1942 r | 1 | Stany Zjednoczone Ameryki | 2) | Pokój Elbow |
2) | Sam Harris | 1967 | 1 | Stany Zjednoczone Ameryki | 3) | Krajobraz moralny |
2) | Sam Harris | 1967 | 1 | Stany Zjednoczone Ameryki | 4 | Waking Up: Przewodnik po duchowości bez religii |
3) | Richard dawkins | 1941 | 2) | Zjednoczone Królestwo | 5 | Magia rzeczywistości: jak wiemy, co jest naprawdę prawdą |
3) | Richard dawkins | 1941 | 2) | Zjednoczone Królestwo | 6 | Apetyt na cud: Dokonywanie naukowca |
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Born { get; set; }
public Country Residience { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Country
{
public int CountryId { get; set; }
public string CountryName { get; set; }
}
public class Book
{
public int BookId { get; set; }
public string BookName { get; set; }
}
Słownik remainingHorsemen
zostanie zapełniony w pełni zmaterializowanymi instancjami obiektów osoby. Dla każdego wiersza wyniku zapytania przekazywane są zmapowane wartości instancji typów zdefiniowanych w argumentach lambda i od Ciebie zależy, jak sobie z tym poradzić.
var sql = @"SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 1 AS BookId, 'Brainstorms' AS BookName
UNION ALL SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 2 AS BookId, 'Elbow Room' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 3 AS BookId, 'The Moral Landscape' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 4 AS BookId, 'Waking Up: A Guide to Spirituality Without Religion' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 5 AS BookId, 'The Magic of Reality: How We Know What`s Really True' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 6 AS BookId, 'An Appetite for Wonder: The Making of a Scientist' AS BookName";
var remainingHorsemen = new Dictionary<int, Person>();
connection.Query<Person, Country, Book, Person>(sql, (person, country, book) => {
//person
Person personEntity;
//trip
if (!remainingHorsemen.TryGetValue(person.Id, out personEntity))
{
remainingHorsemen.Add(person.Id, personEntity = person);
}
//country
if(personEntity.Residience == null)
{
if (country == null)
{
country = new Country { CountryName = "" };
}
personEntity.Residience = country;
}
//books
if(personEntity.Books == null)
{
personEntity.Books = new List<Book>();
}
if (book != null)
{
if (!personEntity.Books.Any(x => x.BookId == book.BookId))
{
personEntity.Books.Add(book);
}
}
return personEntity;
},
splitOn: "CountryId,BookId");
Zwróć uwagę, że argument
splitOn
jest rozdzielaną przecinkami listą pierwszych kolumn następnego typu.
Mapowanie ponad 7 typów
Czasami liczba typów mapowania przekracza 7 podaną przez Func <>, który wykonuje konstrukcję.
Zamiast używać Query<>
z wejściowymi argumentami typu ogólnego, podamy typy do mapowania jako tablicę, a następnie funkcję mapowania. Poza początkowym ręcznym ustawieniem i rzutowaniem wartości, reszta funkcji się nie zmienia.
var sql = @"SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 1 AS BookId, 'Brainstorms' AS BookName
UNION ALL SELECT 1 AS Id, 'Daniel Dennett' AS Name, 1942 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 2 AS BookId, 'Elbow Room' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 3 AS BookId, 'The Moral Landscape' AS BookName
UNION ALL SELECT 2 AS Id, 'Sam Harris' AS Name, 1967 AS Born, 1 AS CountryId, 'United States of America' AS CountryName, 4 AS BookId, 'Waking Up: A Guide to Spirituality Without Religion' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 5 AS BookId, 'The Magic of Reality: How We Know What`s Really True' AS BookName
UNION ALL SELECT 3 AS Id, 'Richard Dawkins' AS Name, 1941 AS Born, 2 AS CountryId, 'United Kingdom' AS CountryName, 6 AS BookId, 'An Appetite for Wonder: The Making of a Scientist' AS BookName";
var remainingHorsemen = new Dictionary<int, Person>();
connection.Query<Person>(sql,
new[]
{
typeof(Person),
typeof(Country),
typeof(Book)
}
, obj => {
Person person = obj[0] as Person;
Country country = obj[1] as Country;
Book book = obj[2] as Book;
//person
Person personEntity;
//trip
if (!remainingHorsemen.TryGetValue(person.Id, out personEntity))
{
remainingHorsemen.Add(person.Id, personEntity = person);
}
//country
if(personEntity.Residience == null)
{
if (country == null)
{
country = new Country { CountryName = "" };
}
personEntity.Residience = country;
}
//books
if(personEntity.Books == null)
{
personEntity.Books = new List<Book>();
}
if (book != null)
{
if (!personEntity.Books.Any(x => x.BookId == book.BookId))
{
personEntity.Books.Add(book);
}
}
return personEntity;
},
splitOn: "CountryId,BookId");
Mapowania niestandardowe
Jeśli nazwy kolumn zapytań nie pasują do twoich klas, możesz ustawić odwzorowania dla typów. W tym przykładzie pokazano mapowanie przy użyciu System.Data.Linq.Mapping.ColumnAttribute
a także niestandardowe mapowanie.
Mapowania należy skonfigurować tylko raz dla każdego typu, więc ustaw je przy uruchamianiu aplikacji lub w innym miejscu, aby były inicjowane tylko raz.
Przyjmując ponownie to samo zapytanie, co w przykładzie Jeden do wielu, a klasy zajęły się lepszymi nazwami:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Born { get; set; }
public Country Residience { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Country
{
[System.Data.Linq.Mapping.Column(Name = "CountryId")]
public int Id { get; set; }
[System.Data.Linq.Mapping.Column(Name = "CountryName")]
public string Name { get; set; }
}
public class Book
{
public int Id { get; set; }
public string Name { get; set; }
}
Zauważ, że
Book
nie polega naColumnAttribute
ale musielibyśmy zachować instrukcjęif
Teraz umieść ten kod mapowania gdzieś w aplikacji, gdzie jest wykonywany tylko raz:
Dapper.SqlMapper.SetTypeMap(
typeof(Country),
new CustomPropertyTypeMap(
typeof(Country),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<System.Data.Linq.Mapping.ColumnAttribute>()
.Any(attr => attr.Name == columnName)))
);
var bookMap = new CustomPropertyTypeMap(
typeof(Book),
(type, columnName) =>
{
if(columnName == "BookId")
{
return type.GetProperty("Id");
}
if (columnName == "BookName")
{
return type.GetProperty("Name");
}
throw new InvalidOperationException($"No matching mapping for {columnName}");
}
);
Dapper.SqlMapper.SetTypeMap(typeof(Book), bookMap);
Następnie zapytanie jest wykonywane przy użyciu dowolnego z poprzednich przykładów Query<>
.
W tej odpowiedzi pokazano prostszy sposób dodawania mapowań.