Consultar la base de datos de datos del modelo usando dapper

c# dapper database sql sql-server

Pregunta

Estoy usando dapper .net como ORM para un proyecto en el que estoy trabajando actualmente y tengo una pregunta sobre consultar la base de datos para obtener información sobre el modelo. Por ejemplo, si tengo un modelo que se ve así:

public class Object
{
    public string Title { get; set; }
    public string Body { get; set; }
    public List<Tag> TagList{ get; set; }
    public List<Industry> IndustryList{ get; set; } 
}

Este modelo contiene una lista de dos objetos:

public class Tag
{
    public int TagID { get; set; }
    public string Name { get; set; }
}

public class Industry
{
    public int IndustryID { get; set; }
    public string Name { get; set; }
}

Como puede ver, la clase Object puede tener una o muchas etiquetas asociadas y una o varias industrias asociadas.

Tengo curiosidad de cómo llenar este modelo con datos de la base de datos. Mi instinto inicial fue que requeriría múltiples consultas. Una llamada para obtener la información del Objeto, una llamada al DB para obtener las etiquetas asociadas con el objeto y una tercera llamada al DB para obtener toda la información de la Industria asociada con el objeto. ¿Hay una manera más fácil o más limpia de hacer esto? Creo que el rendimiento sería bastante pobre en este caso.

Respuesta aceptada

No estás haciendo nada mal, simplemente no es la forma en que se diseñó la API. Todas las API de Query siempre devolverán un objeto por fila de base de datos.

Por lo tanto, esto funciona bien en muchos -> una dirección, pero menos para uno -> muchos multi-mapas.

Hay 2 problemas aquí:

  1. Si presentamos un mapeador incorporado que funcione con su consulta, se esperaría que "descartemos" los datos duplicados. (Contactos. * Está duplicado en su consulta)

  2. Si lo diseñamos para que funcione con un par de uno o varios, necesitaremos algún tipo de mapa de identidad. Lo cual agrega complejidad.


Tomemos, por ejemplo, esta consulta que es eficiente si solo necesita extraer un número limitado de registros, si empuja esto hasta un millón de cosas se vuelve más complicado, porque necesita transmitir y no puede cargar todo en la memoria:

var sql = "set nocount on
DECLARE @t TABLE(ContactID int,  ContactName nvarchar(100))
INSERT @t
SELECT *
FROM Contacts
WHERE clientid=1
set nocount off 
SELECT * FROM @t 
SELECT * FROM Phone where ContactId in (select t.ContactId from @t t)"

Lo que podría hacer es extender el GridReader para permitir la reasignación:

var mapped = cnn.QueryMultiple(sql)
   .Map<Contact,Phone, int>
    (
       contact => contact.ContactID, 
       phone => phone.ContactID,
       (contact, phones) => { contact.Phones = phones };  
    );

Suponiendo que extiende su GridReader y con un asignador:

public static IEnumerable<TFirst> Map<TFirst, TSecond, TKey>
    (
    this GridReader reader,
    Func<TFirst, TKey> firstKey, 
    Func<TSecond, TKey> secondKey, 
    Action<TFirst, IEnumerable<TSecond>> addChildren
    )
{
    var first = reader.Read<TFirst>().ToList();
    var childMap = reader
        .Read<TSecond>()
        .GroupBy(s => secondKey(s))
        .ToDictionary(g => g.Key, g => g.AsEnumerable());

    foreach (var item in first)
    {
        IEnumerable<TSecond> children;
        if(childMap.TryGetValue(firstKey(item), out children))
        {
            addChildren(item,children);
        }
    }

    return first;
}

Dado que esto es un poco complicado y complejo, con advertencias. No estoy inclinado a incluir esto en el núcleo.


Respuesta popular

FYI - Obtuve la respuesta de Sam trabajando haciendo lo siguiente:

Primero, agregué un archivo de clase llamado "Extensions.cs". Tuve que cambiar la palabra clave "this" por "reader" en dos lugares:

using System;
using System.Collections.Generic;
using System.Linq;
using Dapper;

namespace TestMySQL.Helpers
{
    public static class Extensions
    {
        public static IEnumerable<TFirst> Map<TFirst, TSecond, TKey>
            (
            this Dapper.SqlMapper.GridReader reader,
            Func<TFirst, TKey> firstKey,
            Func<TSecond, TKey> secondKey,
            Action<TFirst, IEnumerable<TSecond>> addChildren
            )
        {
            var first = reader.Read<TFirst>().ToList();
            var childMap = reader
                .Read<TSecond>()
                .GroupBy(s => secondKey(s))
                .ToDictionary(g => g.Key, g => g.AsEnumerable());

            foreach (var item in first)
            {
                IEnumerable<TSecond> children;
                if (childMap.TryGetValue(firstKey(item), out children))
                {
                    addChildren(item, children);
                }
            }

            return first;
        }
    }
}

Segundo, agregué el siguiente método, modificando el último parámetro:

public IEnumerable<Contact> GetContactsAndPhoneNumbers()
{
    var sql = @"
SELECT * FROM Contacts WHERE clientid=1
SELECT * FROM Phone where ContactId in (select ContactId FROM Contacts WHERE clientid=1)";

    using (var connection = GetOpenConnection())
    {
        var mapped = connection.QueryMultiple(sql)    
            .Map<Contact,Phone, int>     (        
            contact => contact.ContactID,        
            phone => phone.ContactID,
            (contact, phones) => { contact.Phones = phones; }      
        ); 
        return mapped;
    }
}


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é