Как написать один ко многим запрос в Dapper.Net?



Я написал этот код для проецирования отношения один ко многим, но он не работает:



using (var connection = new SqlConnection(connectionString))
{
connection.Open();

IEnumerable<Store> stores = connection.Query<Store, IEnumerable<Employee>, Store>
(@"Select Stores.Id as StoreId, Stores.Name,
Employees.Id as EmployeeId, Employees.FirstName,
Employees.LastName, Employees.StoreId
from Store Stores
INNER JOIN Employee Employees ON Stores.Id = Employees.StoreId",
(a, s) => { a.Employees = s; return a; },
splitOn: "EmployeeId");

foreach (var store in stores)
{
Console.WriteLine(store.Name);
}
}


может кто-нибудь заметить ошибку?



EDIT:



это мои сущности:



public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public IList<Store> Stores { get; set; }

public Product()
{
Stores = new List<Store>();
}
}

public class Store
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Product> Products { get; set; }
public IEnumerable<Employee> Employees { get; set; }

public Store()
{
Products = new List<Product>();
Employees = new List<Employee>();
}
}


EDIT:



Я меняю запрос на:



            IEnumerable<Store> stores = connection.Query<Store, List<Employee>, Store>
(@"Select Stores.Id as StoreId ,Stores.Name,Employees.Id as EmployeeId,Employees.FirstName,
Employees.LastName,Employees.StoreId from Store Stores INNER JOIN Employee Employees
ON Stores.Id = Employees.StoreId",
(a, s) => { a.Employees = s; return a; }, splitOn: "EmployeeId");


и я избавляюсь от исключений! Однако, сотрудники не отображаются вообще. Я до сих пор не уверен, какие проблемы он имел с IEnumerable<Employee> в первом запросе.

860   6  

6 ответов:

этот пост показывает, как запросить a сильно нормализованная база данных SQL, и сопоставить результат в набор сильно вложенных объектов C# POCO.

Ингредиенты:

  • 8 строк C#.
  • некоторые достаточно простой SQL, который использует некоторые соединения.
  • две потрясающие библиотеки.

понимание, которое позволило мне решить эту проблему, состоит в том, чтобы отделить MicroORM С mapping the result back to the POCO Entities. Таким образом, мы используем два отдельных библиотеки:

по сути, мы используем щеголеватый чтобы запросить базу данных, затем используйте гулящая.Automapper чтобы отобразить результат прямо в наши POCOs.

преимущества

  • простота. Его менее 8 строк кода. Я нахожу это намного легче понять, отладка и изменение.
  • меньше кода. Несколько строк кода-это все гулящая.Automapper нужно обрабатывать все, что вы бросаете на него, даже если у нас есть сложный вложенный POCO (т. е. POCO содержит List<MyClass1> что, в свою очередь, содержит List<MySubClass2> и т. д.).
  • скорость. Обе эти библиотеки имеют экстраординарное количество оптимизации и кэширования, чтобы заставить их работать почти так же быстро, как ручная настройка ADO.NET запросы.
  • разделение. Мы можем изменить Микроорм на другой, и отображение все еще работает, и наоборот.
  • гибкость. гулящая.Automapper обрабатывает произвольно вложенные иерархии, это не ограничивается несколькими уровнями вложенности. Мы можем легко сделать быстрые изменения, и все будет работать.
  • отладка. Сначала мы можем увидеть, что SQL-запрос работая правильно, мы можем проверить, что результат SQL-запроса правильно сопоставлен с целевыми объектами POCO.
  • простота разработки в SQL. Я считаю, что создание сглаженных запросов с inner joins чтобы вернуть плоские результаты гораздо проще, чем создание нескольких операторов select, с прошивкой на стороне клиента.
  • оптимизированные запросы в SQL. В сильно нормализованной базе данных создание плоского запроса позволяет применять механизм SQL расширенные оптимизации в целом, которые обычно не были бы возможны, если бы было построено и запущено много небольших отдельных запросов.
  • доверие. Dapper-это задняя часть для StackOverflow, и, ну, Рэнди Берден немного суперзвезда. Мне нужно еще что-то сказать?
  • скорость разработки. я был в состоянии сделать некоторые чрезвычайно сложные запросы, с большим количеством уровней вложенности, и время разработки было довольно низким.
  • меньше жуки. я написал его однажды, он просто работал, и этот метод теперь помогает привести в действие компанию FTSE. Было так мало кода, что не было никакого неожиданного поведения.

недостатки

  • масштабирование за пределы 1 000 000 строк возвращается. хорошо работает при возврате 1,000,000 строк, чтобы уменьшить трафик между нами и SQL server, мы не должны сглаживать его с помощью inner join (что возвращает дубликаты), мы должны вместо этого использовать несколько select заявления и сшить все вместе на стороне клиента (см. другие ответы на этой странице).
  • этот метод ориентирован на запрос. Я не использовал этот метод для записи в базу данных, но я уверен, что Dapper более чем способен сделать это с некоторой дополнительной работой, так как StackOverflow сам использует Dapper в качестве своего уровня доступа к данным (DAL).

производительность Тестирование

в моих тестах, гулящая.Automapper добавил небольшие накладные расходы на результаты, возвращенные Dapper, что означало, что он все еще был в 10 раз быстрее, чем Entity Framework, и комбинация все еще довольно близка к теоретической максимальной скорости, на которую способен SQL + C#.

в большинстве практических случаев большая часть накладных расходов будет находиться в менее чем оптимальном SQL-запросе, а не с некоторым отображением результатов на C# сторона.

Результаты Испытаний

общее количество итераций: 1000

  • Dapper by itself: 1.889 миллисекунд на запрос, используя 3 lines of code to return the dynamic.
  • Dapper + Slapper.Automapper: 2.463 миллисекунды на запрос, используя дополнительный 3 lines of code for the query + mapping from dynamic to POCO Entities.
Работала

в этом примере у нас есть список Contacts и друг Contact может иметь один или более phone numbers.

сущностей Poco

public class TestContact
{
    public int ContactID { get; set; }
    public string ContactName { get; set; }
    public List<TestPhone> TestPhones { get; set; }
}

public class TestPhone
{
    public int PhoneId { get; set; }
    public int ContactID { get; set; } // foreign key
    public string Number { get; set; }
}

таблица SQL TestContact

enter image description here

таблица SQL TestPhone

обратите внимание, что эта таблица имеет внешний ключ ContactID что относится к TestContact таблица (это соответствует List<TestPhone> в POCO выше).

enter image description here

SQL, который дает плоский результат

в нашем SQL-запросе мы используем как можно больше JOIN заявления, как нам нужно получить все данные, которые нам нужны, в квартиры, денормализованной форме. Да, это может привести к дубликатам в выходных данных, но эти дубликаты будут устранены автоматически при использовании гулящая.Automapper чтобы автоматически отобразить результат этого запроса прямо на нашу карту объектов POCO.

USE [MyDatabase];
    SELECT tc.[ContactID] as ContactID
          ,tc.[ContactName] as ContactName
          ,tp.[PhoneId] AS TestPhones_PhoneId
          ,tp.[ContactId] AS TestPhones_ContactId
          ,tp.[Number] AS TestPhones_Number
          FROM TestContact tc
    INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId

enter image description here

C# код

const string sql = @"SELECT tc.[ContactID] as ContactID
          ,tc.[ContactName] as ContactName
          ,tp.[PhoneId] AS TestPhones_PhoneId
          ,tp.[ContactId] AS TestPhones_ContactId
          ,tp.[Number] AS TestPhones_Number
          FROM TestContact tc
    INNER JOIN TestPhone tp ON tc.ContactId = tp.ContactId";

string connectionString = // -- Insert SQL connection string here.

using (var conn = new SqlConnection(connectionString))
{
    conn.Open();    
    // Can set default database here with conn.ChangeDatabase(...)
    {
        // Step 1: Use Dapper to return the  flat result as a Dynamic.
        dynamic test = conn.Query<dynamic>(sql);

        // Step 2: Use Slapper.Automapper for mapping to the POCO Entities.
        // - IMPORTANT: Let Slapper.Automapper know how to do the mapping;
        //   let it know the primary key for each POCO.
        // - Must also use underscore notation ("_") to name parameters;
        //   see Slapper.Automapper docs.
        Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestContact), new List<string> { "ContactID" });
        Slapper.AutoMapper.Configuration.AddIdentifiers(typeof(TestPhone), new List<string> { "PhoneID" });

        var testContact = (Slapper.AutoMapper.MapDynamic<TestContact>(test) as IEnumerable<TestContact>).ToList();      

        foreach (var c in testContact)
        {                               
            foreach (var p in c.TestPhones)
            {
                Console.Write("ContactName: {0}: Phone: {1}\n", c.ContactName, p.Number);   
            }
        }
    }
}

выход

enter image description here

Poco Entity Иерархия

глядя в Visual Studio, мы видим, что Slapper.Automapper правильно заполнил наши объекты POCO, т. е. у нас есть List<TestContact> и друг TestContact есть List<TestPhone>.

enter image description here

Примечания

как щеголь и гулящая.Automapper кэширует все внутренне для скорости. Если вы столкнулись с проблемами памяти (очень маловероятно), убедитесь, что вы иногда очищаете кэш для обоих из них.

убедитесь, что вы имя столбцы возвращаются, используя подчеркивание (_) обозначения дать гулящая.AutoMapper подсказывает, как отобразить результат в объекты POCO.

убедитесь, что вы даете Slapper.Ключи Automapper к первичному ключу для каждого объекта POCO (см. строки Slapper.AutoMapper.Configuration.AddIdentifiers). Вы также можете использовать Attributes на POCO для этого. Если вы пропустите этот шаг, то он может пойти не так (в теории), как Slapper.Automapper не знал бы, как правильно выполнить отображение.

обновление 2015-06-14

успешно применил этот метод к огромной производственной базе данных с более чем 40 нормализованными таблицами. Он отлично работал для отображения расширенного SQL-запроса с более чем 16 inner join и left join в правильную иерархию POCO (с 4 уровнями вложенности). Запросы слепо быстры, почти так же быстро, как ручное кодирование ADO.NET (обычно это было 52 миллисекунды для запроса и 50 миллисекунд для сопоставления из плоского результата в иерархию POCO). Это действительно ничего революционного, но он наверняка превосходит Entity Framework по скорости и простоте использования, особенно если все, что мы делаем, это запуск запросов.

обновление 2016-02-19

код работает безупречно в производстве в течение 9 месяцев. Последняя версия Slapper.Automapper имеет все изменения, которые я применил, чтобы исправить проблему, связанную с возвращением нулей в SQL-запросе.

обновление 2017-02-20

код работает безупречно в производстве в течение 21 месяца, и обрабатывал непрерывные запросы от сотен пользователей в компании FTSE 250.

Slapper.Automapper также отлично подходит для сопоставления .csv-файл прямо в список POCOs. Читай .csv-файл в список IDictionary, а затем сопоставить его прямо в целевой список POCOs. Единственная хитрость заключается в том, что вы должны добавить свойство int Id {get; set}, и убедитесь, что он уникален для каждой строки (иначе automapper не сможет различать строки).

посмотреть: https://github.com/SlapperAutoMapper/Slapper.AutoMapper

Я хотел, чтобы это было как можно проще, мое решение:

public List<ForumMessage> GetForumMessagesByParentId(int parentId)
{
    var sql = @"
    select d.id_data as Id, d.cd_group As GroupId, d.cd_user as UserId, d.tx_login As Login, 
        d.tx_title As Title, d.tx_message As [Message], d.tx_signature As [Signature], d.nm_views As Views, d.nm_replies As Replies, 
        d.dt_created As CreatedDate, d.dt_lastreply As LastReplyDate, d.dt_edited As EditedDate, d.tx_key As [Key]
    from 
        t_data d
    where d.cd_data = @DataId order by id_data asc;

    select d.id_data As DataId, di.id_data_image As DataImageId, di.cd_image As ImageId, i.fl_local As IsLocal
    from 
        t_data d
        inner join T_data_image di on d.id_data = di.cd_data
        inner join T_image i on di.cd_image = i.id_image 
    where d.id_data = @DataId and di.fl_deleted = 0 order by d.id_data asc;";

    var mapper = _conn.QueryMultiple(sql, new { DataId = parentId });
    var messages = mapper.Read<ForumMessage>().ToDictionary(k => k.Id, v => v);
    var images = mapper.Read<ForumMessageImage>().ToList();

    foreach(var imageGroup in images.GroupBy(g => g.DataId))
    {
        messages[imageGroup.Key].Images = imageGroup.ToList();
    }

    return messages.Values.ToList();
}

Я все еще делаю один вызов базы данных, и хотя теперь я выполняю 2 запроса вместо одного, второй запрос использует внутреннее соединение вместо менее оптимального левого соединения.

по данным ответ там нет никого, чтобы многие отображения поддержки встроены Dapper.Net. запросы всегда возвращают по одному объекту на строку базы данных. Однако есть и альтернативное решение.

небольшая модификация ответа Эндрю, которая использует функцию для выбора родительского ключа вместо GetHashCode.

public static IEnumerable<TParent> QueryParentChild<TParent, TChild, TParentKey>(
    this IDbConnection connection,
    string sql,
    Func<TParent, TParentKey> parentKeySelector,
    Func<TParent, IList<TChild>> childSelector,
    dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
    Dictionary<TParentKey, TParent> cache = new Dictionary<TParentKey, TParent>();

    connection.Query<TParent, TChild, TParent>(
        sql,
        (parent, child) =>
            {
                if (!cache.ContainsKey(parentKeySelector(parent)))
                {
                    cache.Add(parentKeySelector(parent), parent);
                }

                TParent cachedParent = cache[parentKeySelector(parent)];
                IList<TChild> children = childSelector(cachedParent);
                children.Add(child);
                return cachedParent;
            },
        param as object, transaction, buffered, splitOn, commandTimeout, commandType);

    return cache.Values;
}

пример использования

conn.QueryParentChild<Product, Store, int>("sql here", prod => prod.Id, prod => prod.Stores)

вот грубый обходной путь

    public static IEnumerable<TOne> Query<TOne, TMany>(this IDbConnection cnn, string sql, Func<TOne, IList<TMany>> property, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
    {
        var cache = new Dictionary<int, TOne>();
        cnn.Query<TOne, TMany, TOne>(sql, (one, many) =>
                                            {
                                                if (!cache.ContainsKey(one.GetHashCode()))
                                                    cache.Add(one.GetHashCode(), one);

                                                var localOne = cache[one.GetHashCode()];
                                                var list = property(localOne);
                                                list.Add(many);
                                                return localOne;
                                            }, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
        return cache.Values;
    }

это ни в коем случае не самый эффективный способ, но он поможет вам встать и работать. Я постараюсь оптимизировать это, когда у меня будет шанс.

используйте его так:

conn.Query<Product, Store>("sql here", prod => prod.Stores);

имейте в виду, что ваши объекты необходимо реализовать GetHashCode, например:

    public override int GetHashCode()
    {
        return this.Id.GetHashCode();
    }

вот еще один способ:

Order (one) - OrderDetail (many)

using (var connection = new SqlCeConnection(connectionString))
{           
    var orderDictionary = new Dictionary<int, Order>();

    var list = connection.Query<Order, OrderDetail, Order>(
        sql,
        (order, orderDetail) =>
        {
            Order orderEntry;

            if (!orderDictionary.TryGetValue(order.OrderID, out orderEntry))
            {
                orderEntry = order;
                orderEntry.OrderDetails = new List<OrderDetail>();
                orderDictionary.Add(orderEntry.OrderID, orderEntry);
            }

            orderEntry.OrderDetails.Add(orderDetail);
            return orderEntry;
        },
        splitOn: "OrderDetailID")
    .Distinct()
    .ToList();
}

источник: http://dapper-tutorial.net/result-multi-mapping#example---query-multi-mapping-one-to-many

Comments

    Ничего не найдено.