Ricercare il database per i dati del modello usando dapper

c# dapper database sql sql-server

Domanda

Sto usando dapper .net come un ORM per un progetto al quale sto lavorando attualmente e ho una domanda sull'interrogazione del database per informazioni sul modello. Per esempio se ho un modello che assomiglia a questo:

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

Questo modello contiene un elenco di due oggetti:

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

Come puoi vedere, la classe Object può avere uno o più tag associati e uno o più settori associati.

Sono curioso di come riempire questo modello con i dati del database. Il mio istinto iniziale era che avrebbe richiesto più domande. Una chiamata per ottenere le informazioni dell'oggetto, una chiamata DB per ottenere i tag associati all'oggetto e una terza chiamata DB per ottenere tutte le informazioni sull'industria associate all'oggetto. C'è un modo più semplice o più pulito per farlo? Sento che la performance sarebbe piuttosto scarsa in questo caso.

Risposta accettata

Non stai facendo nulla di sbagliato, non è solo il modo in cui è stata progettata l'API. Tutte le API Query restituiranno sempre un oggetto per riga di database.

Quindi, questo funziona bene su molti -> una direzione, ma meno bene per quella -> molte multi-mappa.

Ci sono 2 problemi qui:

  1. Se introduciamo un mapper integrato che funziona con la tua query, ci si aspetterebbe di "scartare" i dati duplicati. (Contatti. * È duplicato nella tua query)

  2. Se lo progettiamo per funzionare con una -> molte coppie, avremo bisogno di una sorta di mappa delle identità. Il che aggiunge complessità.


Prendi ad esempio questa query che è efficiente se hai solo bisogno di tirare un numero limitato di record, se la spendi su un milione di cose diventano più complicate, perché devi eseguire lo streaming e non puoi caricare tutto in 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)"

Quello che potresti fare è estendere GridReader per consentire la rimappatura:

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

Supponendo di estendere il tuo GridReader e con un mappatore:

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

Poiché questo è un po 'complicato e complesso, con avvertenze. Non mi sto propendendo per includere questo nel nucleo.


Risposta popolare

A proposito, ho ottenuto la risposta di Sam lavorando nel modo seguente:

Per prima cosa ho aggiunto un file di classe chiamato "Extensions.cs". Ho dovuto cambiare la parola chiave "this" in "reader" in due punti:

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

Secondo, ho aggiunto il seguente metodo, modificando l'ultimo parametro:

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


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow