¿Cómo escribo una a muchas consultas en Dapper.Net?

.net c# dapper

Pregunta

He escrito este código para proyectar una relación de uno a muchos, pero no está funcionando:

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

¿Alguien puede detectar el error?

EDITAR:

Estas son mis entidades:

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

EDITAR:

Cambio la consulta a:

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

¡y me deshago de las excepciones! Sin embargo, los empleados no están mapeados en absoluto. Todavía no estoy seguro de qué problema tenía con IEnumerable<Employee> en la primera consulta.

Respuesta aceptada

Esta publicación muestra cómo consultar una base de datos SQL altamente normalizada y asignar el resultado a un conjunto de objetos POCO de C # altamente anidados.

Ingredientes:

  • 8 líneas de C #.
  • Algunos SQL razonablemente simples que usan algunas combinaciones.
  • Dos bibliotecas impresionantes.

La información que me permitió resolver este problema es separar el MicroORM de la mapping the result back to the POCO Entities . Por lo tanto, utilizamos dos bibliotecas separadas:

Esencialmente, usamos Dapper para consultar la base de datos, luego usamos Slapper.Automapper para mapear el resultado directamente en nuestros POCOs.

Ventajas

  • La simplicidad Sus menos de 8 líneas de código. Encuentro esto mucho más fácil de entender, depurar y cambiar.
  • Menos código . Un par de líneas de código es todo lo que Slapper.Automapper necesita para manejar cualquier cosa que le lance, incluso si tenemos un POCO anidado complejo (es decir, POCO contiene la List<MyClass1> que a su vez contiene la List<MySubClass2> , etc.).
  • La velocidad Ambas bibliotecas tienen una extraordinaria cantidad de optimización y almacenamiento en caché para hacer que se ejecuten casi tan rápido como las consultas ADO.NET ajustadas a mano.
  • Separación de inquietudes . Podemos cambiar el MicroORM por otro diferente, y el mapeo aún funciona, y viceversa.
  • Flexibilidad Slapper.Automapper maneja jerarquías anidadas arbitrariamente, no se limita a un par de niveles de anidación. Fácilmente podemos hacer cambios rápidos, y todo seguirá funcionando.
  • Depuración Primero podemos ver que la consulta SQL está funcionando correctamente, luego podemos verificar que el resultado de la consulta SQL se asigne correctamente a las Entidades POCO de destino.
  • Facilidad de desarrollo en SQL . Encuentro que crear consultas planas con inner joins para devolver resultados planos es mucho más fácil que crear múltiples declaraciones de selección, con puntadas en el lado del cliente.
  • Consultas optimizadas en SQL . En una base de datos altamente normalizada, la creación de una consulta plana permite que el motor SQL aplique optimizaciones avanzadas al conjunto, lo que normalmente no sería posible si se construyeran y ejecutaran muchas consultas individuales pequeñas.
  • Confianza Dapper es el extremo posterior de StackOverflow y, bueno, Randy Burden es un poco una superestrella. ¿Necesito decir algo más?
  • Velocidad de desarrollo. Pude hacer algunas consultas extraordinariamente complejas, con muchos niveles de anidamiento, y el tiempo de desarrollo fue bastante bajo.
  • Menos bichos. Lo escribí una vez, simplemente funcionó, y esta técnica ahora está ayudando a impulsar una empresa FTSE. Había tan poco código que no había comportamiento inesperado.

Desventajas

  • Escalado más allá de 1,000,000 de filas devueltas. Funciona bien cuando se devuelven <100,000 filas. Sin embargo, si estamos recuperando> 1,000,000 de filas, con el fin de reducir el tráfico entre nosotros y el servidor SQL, no deberíamos aplanarlo utilizando inner join (lo que trae duplicados), deberíamos usar múltiples declaraciones de select y volver a unir todo. juntos en el lado del cliente (ver las otras respuestas en esta página).
  • Esta técnica está orientada a la consulta . No he usado esta técnica para escribir en la base de datos, pero estoy seguro de que Dapper es más que capaz de hacer esto con un poco más de trabajo extra, ya que StackOverflow utiliza Dapper como su Capa de acceso a datos (DAL).

Pruebas de rendimiento

En mis pruebas, Slapper.Automapper agregó una pequeña sobrecarga a los resultados devueltos por Dapper, lo que significaba que todavía era 10 veces más rápido que Entity Framework, y la combinación aún está bastante cerca de la velocidad máxima teórica que SQL + C # es capaz de hacer .

En la mayoría de los casos prácticos, la mayor parte de la sobrecarga estaría en una consulta de SQL menos que óptima, y no con un mapeo de los resultados en el lado C #.

Resultados de las pruebas de rendimiento

Número total de iteraciones: 1000

  • Dapper by itself : 1.889 milisegundos por consulta, usando 3 lines of code to return the dynamic .
  • Dapper + Slapper.Automapper : 2.463 milisegundos por consulta, utilizando 3 lines of code for the query + mapping from dynamic to POCO Entities adicionales 3 lines of code for the query + mapping from dynamic to POCO Entities .

Ejemplo trabajado

En este ejemplo, tenemos una lista de Contacts y cada Contact puede tener uno o más phone numbers .

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

Tabla SQL TestContact

introduzca la descripción de la imagen aquí

SQL Table TestPhone

Tenga en cuenta que esta tabla tiene una clave externa ContactID que se refiere a la tabla TestContact (esto corresponde a la List<TestPhone> en el POCO anterior).

introduzca la descripción de la imagen aquí

SQL que produce resultados planos

En nuestra consulta SQL, usamos tantas instrucciones de JOIN como necesitamos para obtener todos los datos que necesitamos, en una forma plana y desnormalizada . Sí, esto podría producir duplicados en la salida, pero estos duplicados se eliminarán automáticamente cuando usamos Slapper.Automapper para mapear automáticamente el resultado de esta consulta directamente en nuestro mapa de objetos 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

introduzca la descripción de la imagen aquí

Código 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 in the SQL query;
        //   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);   
            }
        }
    }
}

Salida

introduzca la descripción de la imagen aquí

Jerarquía de la entidad POCO

Mirando en Visual Studio, podemos ver que Slapper.Automapper ha poblado correctamente nuestras Entidades POCO, es decir, tenemos una List<TestContact> , y cada TestContact tiene una List<TestPhone> .

introduzca la descripción de la imagen aquí

Notas

Tanto Dapper como Slapper.Automapper almacenan en caché todo lo interno para la velocidad. Si tiene problemas de memoria (muy poco probable), asegúrese de borrar ocasionalmente la memoria caché para ambos.

Asegúrese de nombrar las columnas que regresan, usando la notación de subrayado ( _ ) para dar pistas a Slapper.Automapper sobre cómo asignar el resultado a las Entidades POCO.

Asegúrese de proporcionar pistas de Slapper.Automapper en la clave principal para cada Entidad POCO (consulte las líneas Slapper.AutoMapper.Configuration.AddIdentifiers ). También puede utilizar Attributes en el POCO para esto. Si omite este paso, podría salir mal (en teoría), ya que Slapper.Automapper no sabría cómo hacer el mapeo correctamente.

Actualización 2015-06-14

Se aplicó con éxito esta técnica a una gran base de datos de producción con más de 40 tablas normalizadas. Funcionó perfectamente para asignar una consulta SQL avanzada con más de 16 inner join y una left join en la jerarquía de POCO adecuada (con 4 niveles de anidación). Las consultas son increíblemente rápidas, casi tan rápidas como la codificación manual en ADO.NET (en general, fueron 52 milisegundos para la consulta y 50 milisegundos para la asignación del resultado plano a la jerarquía de POCO). Esto realmente no es nada revolucionario, pero seguro que supera a Entity Framework en cuanto a velocidad y facilidad de uso, especialmente si todo lo que estamos haciendo es ejecutar consultas.

Actualización 2016-02-19

El código ha estado funcionando sin problemas en producción durante 9 meses. La última versión de Slapper.Automapper tiene todos los cambios que apliqué para solucionar el problema relacionado con los valores nulos que se devuelven en la consulta SQL.

Actualización 2017-02-20

El código ha estado funcionando sin problemas en producción durante 21 meses y ha manejado las consultas continuas de cientos de usuarios en una compañía FTSE 250.

Slapper.Automapper también es ideal para mapear un archivo .csv directamente en una lista de POCOs. Lea el archivo .csv en una lista de IDictionary, luego asigne directamente a la lista objetivo de POCOs. El único truco es que tiene que agregar una int Id {get; set} , y asegúrese de que sea único para cada fila (o, de lo contrario, el automapper no podrá distinguir entre las filas).

Actualización 2019-01-29

Actualización menor para añadir más comentarios de código.

Consulte: https://github.com/SlapperAutoMapper/Slapper.AutoMapper


Respuesta popular

Quería mantenerlo lo más simple posible, mi solución:

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

Todavía hago una llamada a la base de datos, y mientras ahora ejecuto 2 consultas en lugar de una, la segunda consulta usa una combinación INNER en lugar de una combinación LEFT menos óptima.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué