Abfrage der Datenbank nach Modelldaten mit Hilfe von dapper

c# dapper database sql sql-server

Frage

Ich verwende dapper .net als ORM für ein Projekt, an dem ich gerade arbeite, und ich habe eine Frage zum Abfragen der Datenbank nach Modellinformationen. Zum Beispiel, wenn ich ein Modell habe, das so aussieht:

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

Dieses Modell enthält eine Liste von zwei Objekten:

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

Wie Sie sehen können, können der Object-Klasse eine oder mehrere Tags zugeordnet sein und einer oder mehreren Branchen zugeordnet sein.

Ich bin gespannt, wie man dieses Modell mit Daten aus der Datenbank füllt. Mein erster Instinkt war, dass es mehrere Abfragen erfordern würde. Ein Aufruf zum Abrufen der Objektinformationen, ein DB-Aufruf zum Abrufen der dem Objekt zugeordneten Tags und ein dritter DB-Aufruf zum Abrufen aller dem Objekt zugeordneten Industrieinformationen. Gibt es einen einfacheren oder saubereren Weg, dies zu tun? Ich denke, die Leistung wäre in diesem Fall ziemlich schlecht.

Akzeptierte Antwort

Sie machen nichts falsch, es ist einfach nicht die Art, wie die API entworfen wurde. Alle Query APIs geben immer ein Objekt pro Datenbankzeile zurück.

Also, das funktioniert gut auf den vielen -> eine Richtung, aber weniger gut für die eine -> viele Multi-Map.

Es gibt 2 Probleme hier:

  1. Wenn wir einen eingebauten Mapper einführen, der mit Ihrer Anfrage zusammenarbeitet, würden wir erwarten, dass doppelte Daten "verwerfen". (Kontakte. * Ist in Ihrer Abfrage dupliziert)

  2. Wenn wir es so gestalten, dass es mit einem Ein -> Viele Paar arbeitet, brauchen wir eine Art Identitätskarte. Das fügt Komplexität hinzu.


Nehmen Sie zum Beispiel diese Abfrage, die effizient ist, wenn Sie nur eine begrenzte Anzahl von Datensätzen ziehen müssen, wenn Sie dies auf eine Million Sachen schieben, werden Sie kniffliger, weil Sie streamen müssen und nicht alles in den Speicher laden können:

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

Sie können den GridReader , um die Neuzuordnung zu ermöglichen:

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

Angenommen, Sie erweitern Ihren GridReader und mit einem Mapper:

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

Da dies ein bisschen schwierig und komplex ist, mit Vorbehalten. Ich bin nicht geneigt, dies im Kern aufzunehmen.


Beliebte Antwort

FYI - Ich habe Sams Antwort funktioniert, indem ich Folgendes mache:

Zuerst habe ich eine Klassendatei mit dem Namen "Extensions.cs" hinzugefügt. Ich musste das Schlüsselwort "this" an zwei Stellen in "reader" ändern:

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

Zweitens habe ich die folgende Methode hinzugefügt und den letzten Parameter geändert:

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


Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum