Nesne hiyerarşisi oluşturmak için Çoklu Eşleyici


82

Bir süredir bununla uğraşıyorum , çünkü belgelenmiş gönderiler / kullanıcılar örneğine çok benziyor gibi görünüyor , ancak biraz farklı ve benim için çalışmıyor.

Aşağıdaki basitleştirilmiş kurulum varsayıldığında (bir kişinin birden fazla telefon numarası vardır):

public class Contact
{
    public int ContactID { get; set; }
    public string ContactName { get; set; }
    public IEnumerable<Phone> Phones { get; set; }
}

public class Phone
{
    public int PhoneId { get; set; }
    public int ContactID { get; set; } // foreign key
    public string Number { get; set; }
    public string Type { get; set; }
    public bool IsActive { get; set; }
}

Birden fazla Telefon nesnesiyle bir Kişi döndüren bir şey elde etmeyi çok isterim. Bu şekilde, her birinde 2 telefon bulunan 2 kişim olsaydı, SQL'im toplam 4 satırlık bir sonuç kümesi olarak bunların birleşimini döndürürdü. Ardından Dapper, her birinde iki telefon bulunan 2 temas nesnesini çıkarır.

Depolanan yordamdaki SQL:

SELECT *
FROM Contacts
    LEFT OUTER JOIN Phones ON Phones.ReferenceId=Contacts.ReferenceId
WHERE clientid=1

Bunu denedim, ancak 4 Tuple ile sonuçlandım (bu sorun değil, ama umduğum şey değil ... sadece sonucu yeniden normalleştirmem gerektiği anlamına geliyor):

var x = cn.Query<Contact, Phone, Tuple<Contact, Phone>>("sproc_Contacts_SelectByClient",
                              (co, ph) => Tuple.Create(co, ph), 
                                          splitOn: "PhoneId", param: p, 
                                          commandType: CommandType.StoredProcedure);

ve başka bir yöntemi (aşağıda) denediğimde, "System.Collections.Generic.IEnumerable`1 [Phone] 'yazmak için' System.Int32 'türündeki nesne dönüştürülemiyor."

var x = cn.Query<Contact, IEnumerable<Phone>, Contact>("sproc_Contacts_SelectByClient",
                               (co, ph) => { co.Phones = ph; return co; }, 
                                             splitOn: "PhoneId", param: p,
                                             commandType: CommandType.StoredProcedure);

Yanlış bir şey mi yapıyorum? Ebeveynden çocuğa değil de çocuğa geçmem dışında, tıpkı gönderiler / sahip örneği gibi görünüyor.

Şimdiden teşekkürler

Yanıtlar:


69

Yanlış bir şey yapmıyorsunuz, bu sadece API'nin tasarlanma şekli değil. Tüm QueryAPI'ler her zaman veritabanı satırı başına bir nesne döndürür.

Yani, bu birçok -> tek yönde iyi işliyor, ancak bir -> birçok çoklu harita için daha az iyi.

Burada 2 sorun var:

  1. Sorgunuzla çalışan yerleşik bir eşleyici sunarsak, yinelenen verileri "atmamız" beklenir. (Kişiler. * Sorgunuzda yineleniyor)

  2. Bir -> çok çift ile çalışacak şekilde tasarlarsak, bir çeşit kimlik haritasına ihtiyacımız olacak. Hangi karmaşıklık ekler.


Örneğin, sınırlı sayıda kayıt çekmeniz gerekiyorsa verimli olan bu sorguyu ele alalım, bunu bir milyona kadar zorlarsanız, akışa ihtiyaç duyuyor ve her şeyi belleğe yükleyemiyorsunuz:

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

Yapabilecekleriniz, GridReaderyeniden eşlemeye izin vermek için genişletmektir :

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

GridReader'ınızı bir eşleyici ile genişlettiğinizi varsayarsak:

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

Bu biraz zor ve karmaşık olduğu için uyarılarla birlikte. Bunu öze dahil etmeye meyilli değilim.


Çok havalı. Bu şey epey bir güce sahip ... Sanırım nasıl kullanılacağına alışmaya başladı. Sorgularımın yüküne bakacağım ve sonuç kümelerinin ne kadar büyük olduğunu göreceğim ve birden fazla sorguya sahip olmanın ve bunları birlikte haritalamanın gücünün olup olmadığını göreceğim.
Jorin

@Jorin, diğer seçeneğin birden fazla bağlantıyı düzenlemek ve sonuçları örmek olabilir. Bu biraz daha zor.
Sam Saffron

1
Ayrıca if (childMap. TryGetvalue (..)) 'den sonra başka bir tane eklerim, böylece alt koleksiyon, alt öğe yoksa NULL yerine boş bir koleksiyonla başlatılır. Bunun gibi: else {addChildren (öğe, yeni TChild [] {}); }
Marius

1
@SamSaffron Dapper'ı seviyorum. Teşekkür ederim. Yine de bir sorum var. Birden çoğa, SQL sorgularında yaygın bir durumdur. Tasarımda, uygulayıcının kullanması için aklınızda ne vardı? Bunu zarif bir şekilde yapmak istiyorum, ancak şu anda SQL tarzındayım. Bunun, One Side'ın genellikle "sürücü" olduğu SQL'den geldiğini nasıl düşünebilirim? Dapper'da Many tarafı neden bu kadar? Amaç, nesneyi alıp gerçeğin ardından ayrıştırmamız için mi? Harika kütüphane için teşekkürler.
johnny

2
İş için doğru aleti kullandığınızdan emin olun. Çok büyük veritabanı performans gereksinimleriniz yoksa veya sisteminizi karşılaştırmadıysanız, Dapper'ı kullanarak saatlerinizi veya belki de hayatınızın günlerini boşa harcadınız.
Aluan Haddad

32

Bilginize - Aşağıdakileri yaparak Sam'in cevabını aldım:

Önce "Extensions.cs" adlı bir sınıf dosyası ekledim. "Bu" anahtar kelimesini iki yerde "okuyucu" olarak değiştirmem gerekiyordu:

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

İkinci olarak, son parametreyi değiştirerek aşağıdaki yöntemi ekledim:

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

24

Https://www.tritac.com/blog/dappernet-by-example/ adresine göz atın. Bunun gibi bir şey yapabilirsiniz:

public class Shop {
  public int? Id {get;set;}
  public string Name {get;set;}
  public string Url {get;set;}
  public IList<Account> Accounts {get;set;}
}

public class Account {
  public int? Id {get;set;}
  public string Name {get;set;}
  public string Address {get;set;}
  public string Country {get;set;}
  public int ShopId {get;set;}
}

var lookup = new Dictionary<int, Shop>()
conn.Query<Shop, Account, Shop>(@"
                  SELECT s.*, a.*
                  FROM Shop s
                  INNER JOIN Account a ON s.ShopId = a.ShopId                    
                  ", (s, a) => {
                       Shop shop;
                       if (!lookup.TryGetValue(s.Id, out shop)) {
                           lookup.Add(s.Id, shop = s);
                       }
                       shop.Accounts.Add(a);
                       return shop;
                   },
                   ).AsQueryable();
var resultList = lookup.Values;

Bunu dapper.net testlerinden aldım: https://code.google.com/p/dapper-dot-net/source/browse/Tests/Tests.cs#1343


2
Vaov! Benim için bunu en kolay çözüm olarak buldum. Verilen, bir-> çok için (iki tablo varsayarak), çift seçimlerle giderdim. Ancak benim durumumda bir-> bir-> çok var ve bu harika çalışıyor. Şimdi, çok fazla gereksiz veriyi geri getiriyor, ancak benim durumum için bu fazlalık nispeten küçük - en iyi ihtimalle 10 satır.
kod5

Bu, iki seviye için iyi çalışıyor, ancak daha fazlasına sahip olduğunuzda zorlaşıyor.
Samir Aguiar

1
Alt veri yoksa, (s, a) kodu a = null ile çağrılır ve Hesaplar boş olmak yerine boş giriş içeren bir liste içerir. "Shop.Accounts.Add (a)" öncesinde "if (a! = Null)" eklemeniz gerekir
Etienne

12

Çoklu sonuç seti desteği

Sizin durumunuzda, birden çok sonuç kümesine sahip bir sorguya sahip olmak çok daha iyi (ve aynı zamanda daha kolay) olacaktır. Bu basitçe, iki seçme ifadesi yazmanız gerektiği anlamına gelir:

  1. Kişileri döndüren biri
  2. Ve telefon numaralarını döndüren biri

Bu şekilde nesneleriniz benzersiz olur ve kopyalanmaz.


1
Diğer cevaplar kendilerine göre zarif olabilirken, bunu sevme eğilimindeyim çünkü kodun akıl yürütmesi daha kolay. Bir avuç seçili ifade ve yaklaşık 30 satırlık foreach / linq kodu ile birkaç seviye derinliğinde bir hiyerarşi oluşturabilirim. Bu, büyük sonuç kümeleri ile bozulabilir, ancak neyse ki bu problemim yok (henüz).
Sam Storie

10

İşte kullanımı oldukça kolay olan yeniden kullanılabilir bir çözüm. Andrews cevabının küçük bir değişikliğidir .

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

Örnek kullanım

public class Contact
{
    public int ContactID { get; set; }
    public string ContactName { get; set; }
    public List<Phone> Phones { get; set; } // must be IList

    public Contact()
    {
        this.Phones = new List<Phone>(); // POCO is responsible for instantiating child list
    }
}

public class Phone
{
    public int PhoneID { get; set; }
    public int ContactID { get; set; } // foreign key
    public string Number { get; set; }
    public string Type { get; set; }
    public bool IsActive { get; set; }
}

conn.QueryParentChild<Contact, Phone, int>(
    "SELECT * FROM Contact LEFT OUTER JOIN Phone ON Contact.ContactID = Phone.ContactID",
    contact => contact.ContactID,
    contact => contact.Phones,
    splitOn: "PhoneId");

7

Sam Saffron'un (ve Mike Gleason'un) yaklaşımına dayanarak, burada birden çok çocuğa ve birden çok seviyeye izin verecek bir çözüm var.

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

namespace TestMySQL.Helpers
{
    public static class Extensions
    {
        public static IEnumerable<TFirst> MapChild<TFirst, TSecond, TKey>
            (
            this SqlMapper.GridReader reader,
            List<TFirst> parent,
            List<TSecond> child,
            Func<TFirst, TKey> firstKey,
            Func<TSecond, TKey> secondKey,
            Action<TFirst, IEnumerable<TSecond>> addChildren
            )
        {
            var childMap = child
                .GroupBy(secondKey)
                .ToDictionary(g => g.Key, g => g.AsEnumerable());
            foreach (var item in parent)
            {
                IEnumerable<TSecond> children;
                if (childMap.TryGetValue(firstKey(item), out children))
                {
                    addChildren(item, children);
                }
            }
            return parent;
        }
    }
}

Ardından, işlevin dışında okumasını sağlayabilirsiniz.

using (var multi = conn.QueryMultiple(sql))
{
    var contactList = multi.Read<Contact>().ToList();
    var phoneList = multi.Read<Phone>().ToList;
    contactList = multi.MapChild
        (
            contactList,
            phoneList,
            contact => contact.Id, 
            phone => phone.ContactId,
            (contact, phone) => {contact.Phone = phone;}
        ).ToList();
    return contactList;
}

Eşleme işlevi daha sonra aynı ana nesne kullanılarak bir sonraki alt nesne için yeniden çağrılabilir. Ayrıca , harita işlevinden bağımsız olarak üst veya alt okuma ifadelerine bölmeler uygulayabilirsiniz .

İşte 'tekden N'ye' ek uzatma yöntemi

    public static TFirst MapChildren<TFirst, TSecond, TKey>
        (
        this SqlMapper.GridReader reader,
        TFirst parent,
        IEnumerable<TSecond> children,
        Func<TFirst, TKey> firstKey,
        Func<TSecond, TKey> secondKey,
        Action<TFirst, IEnumerable<TSecond>> addChildren
        )
    {
        if (parent == null || children == null || !children.Any())
        {
            return parent;
        }

        Dictionary<TKey, IEnumerable<TSecond>> childMap = children
            .GroupBy(secondKey)
            .ToDictionary(g => g.Key, g => g.AsEnumerable());

        if (childMap.TryGetValue(firstKey(parent), out IEnumerable<TSecond> foundChildren))
        {
            addChildren(parent, foundChildren);
        }

        return parent;
    }

2
Bunun için teşekkürler - harika bir çözüm. if ifadesini kaldırdı, böylece hiçbir çocuk üzerinde addChilder'ı çağırmamak yerine, çağıran işlev boşları işleyebilir. Bu şekilde çalışması çok daha kolay olan boş listeler ekleyebilirim.
Mladen Mihajlovic

1
Bu harika bir çözüm. "Dinamik bulma" ile ilgili bazı sorunlar yaşadım. Bu contactList = multi.MapChild <Contact, Phone, int> ile çözülebilir (/ * yukarıdakiyle aynı kod * /
granadaCoder

4

DataAccessLayer'ımızı depolanan prosedürlere taşımaya karar verdiğimizde ve bu prosedürler genellikle birden çok bağlantılı sonuç döndürür (aşağıdaki örnek).

Benim yaklaşımım neredeyse aynı, ama belki biraz daha rahat.

Kodunuz şöyle görünebilir:

using ( var conn = GetConn() )
{
    var res = await conn
        .StoredProc<Person>( procName, procParams )
        .Include<Book>( ( p, b ) => p.Books = b.Where( x => x.PersonId == p.Id ).ToList() )
        .Include<Course>( ( p, c ) => p.Courses = c.Where( x => x.PersonId == p.Id ).ToList() )
        .Include<Course, Mark>( ( c, m ) => c.Marks = m.Where( x => x.CourseId == c.Id ).ToList() )
        .Execute();
}


Parçalayalım ...

Uzantı:

public static class SqlExtensions
{
    public static StoredProcMapper<T> StoredProc<T>( this SqlConnection conn, string procName, object procParams )
    {
        return StoredProcMapper<T>
            .Create( conn )
            .Call( procName, procParams );
    }
}

Eşleyici:

public class StoredProcMapper<T>
{
    public static StoredProcMapper<T> Create( SqlConnection conn )
    {
        return new StoredProcMapper<T>( conn );
    }

    private List<MergeInfo> _merges = new List<MergeInfo>();

    public SqlConnection Connection { get; }
    public string ProcName { get; private set; }
    public object Parameters { get; private set; }

    private StoredProcMapper( SqlConnection conn )
    {
        Connection = conn;
        _merges.Add( new MergeInfo( typeof( T ) ) );
    }

    public StoredProcMapper<T> Call( object procName, object parameters )
    {
        ProcName = procName.ToString();
        Parameters = parameters;

        return this;
    }

    public StoredProcMapper<T> Include<TChild>( MergeDelegate<T, TChild> mapper )
    {
        return Include<T, TChild>( mapper );
    }

    public StoredProcMapper<T> Include<TParent, TChild>( MergeDelegate<TParent, TChild> mapper )
    {
        _merges.Add( new MergeInfo<TParent, TChild>( mapper ) );
        return this;
    }

    public async Task<List<T>> Execute()
    {
        if ( string.IsNullOrEmpty( ProcName ) )
            throw new Exception( $"Procedure name not specified! Please use '{nameof(Call)}' method before '{nameof( Execute )}'" );

        var gridReader = await Connection.QueryMultipleAsync( 
            ProcName, Parameters, commandType: CommandType.StoredProcedure );

        foreach ( var merge in _merges )
        {
            merge.Result = gridReader
                .Read( merge.Type )
                .ToList();
        }

        foreach ( var merge in _merges )
        {
            if ( merge.ParentType == null )
                continue;

            var parentMerge = _merges.FirstOrDefault( x => x.Type == merge.ParentType );

            if ( parentMerge == null )
                throw new Exception( $"Wrong parent type '{merge.ParentType.FullName}' for type '{merge.Type.FullName}'." );

            foreach ( var parent in parentMerge.Result )
            {
                merge.Merge( parent, merge.Result );
            }
        }

        return _merges
            .First()
            .Result
            .Cast<T>()
            .ToList();
    }

    private class MergeInfo
    {
        public Type Type { get; }
        public Type ParentType { get; }
        public IEnumerable Result { get; set; }

        public MergeInfo( Type type, Type parentType = null )
        {
            Type = type;
            ParentType = parentType;
        }

        public void Merge( object parent, IEnumerable children )
        {
            MergeInternal( parent, children );
        }

        public virtual void MergeInternal( object parent, IEnumerable children )
        {

        }
    }

    private class MergeInfo<TParent, TChild> : MergeInfo
    {
        public MergeDelegate<TParent, TChild> Action { get; }

        public MergeInfo( MergeDelegate<TParent, TChild> mergeAction )
            : base( typeof( TChild ), typeof( TParent ) )
        {
            Action = mergeAction;
        }

        public override void MergeInternal( object parent, IEnumerable children )
        {
            Action( (TParent)parent, children.Cast<TChild>() );
        }
    }

    public delegate void MergeDelegate<TParent, TChild>( TParent parent, IEnumerable<TChild> children );
}

Hepsi bu, ancak hızlı test yapmak istiyorsanız, işte size uygun modeller ve prosedür:

Modeller:

public class Person
{
    public Guid Id { get; set; }
    public string Name { get; set; }

    public List<Course> Courses { get; set; }
    public List<Book> Books { get; set; }

    public override string ToString() => Name;
}

public class Book
{
    public Guid Id { get; set; }
    public Guid PersonId { get; set; }
    public string Name { get; set; }

    public override string ToString() => Name;
}

public class Course
{
    public Guid Id { get; set; }
    public Guid PersonId { get; set; }
    public string Name { get; set; }

    public List<Mark> Marks { get; set; }

    public override string ToString() => Name;
}

public class Mark
{
    public Guid Id { get; set; }
    public Guid CourseId { get; set; }
    public int Value { get; set; }

    public override string ToString() => Value.ToString();
}

SP:

if exists ( 
    select * 
    from sysobjects 
    where  
        id = object_id(N'dbo.MultiTest')
        and ObjectProperty( id, N'IsProcedure' ) = 1 )
begin
    drop procedure dbo.MultiTest
end
go

create procedure dbo.MultiTest
    @PersonId UniqueIdentifier
as
begin

    declare @tmpPersons table 
    (
        Id UniqueIdentifier,
        Name nvarchar(50)
    );

    declare @tmpBooks table 
    (
        Id UniqueIdentifier,
        PersonId UniqueIdentifier,
        Name nvarchar(50)
    )

    declare @tmpCourses table 
    (
        Id UniqueIdentifier,
        PersonId UniqueIdentifier,
        Name nvarchar(50)
    )

    declare @tmpMarks table 
    (
        Id UniqueIdentifier,
        CourseId UniqueIdentifier,
        Value int
    )

--------------------------------------------------

    insert into @tmpPersons
    values
        ( '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Иван' ),
        ( '467953a5-cb5f-4d06-9fad-505b3bba2058', N'Василий' ),
        ( '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Алефтина' )


    insert into @tmpBooks
    values
        ( NewId(), '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Книга Математика' ),
        ( NewId(), '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Книга Физика' ),
        ( NewId(), '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Книга Геометрия' ),

        ( NewId(), '467953a5-cb5f-4d06-9fad-505b3bba2058', N'Книга Биология' ),
        ( NewId(), '467953a5-cb5f-4d06-9fad-505b3bba2058', N'Книга Химия' ),

        ( NewId(), '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Книга История' ),
        ( NewId(), '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Книга Литература' ),
        ( NewId(), '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Книга Древне-шумерский диалект иврита' )


    insert into @tmpCourses
    values
        ( '30945b68-a6ef-4da8-9a35-d3b2845e7de3', '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Математика' ),
        ( '7881f090-ccd6-4fb9-a1e0-ff4ff5c18450', '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Физика' ),
        ( '92bbefd1-9fec-4dc7-bb58-986eadb105c8', '576fb8e8-41a2-43a9-8e77-a8213aa6e387', N'Геометрия' ),

        ( '923a2f0c-c5c7-4394-847c-c5028fe14711', '467953a5-cb5f-4d06-9fad-505b3bba2058', N'Биология' ),
        ( 'ace50388-eb05-4c46-82a9-5836cf0c988c', '467953a5-cb5f-4d06-9fad-505b3bba2058', N'Химия' ),

        ( '53ea69fb-6cc4-4a6f-82c2-0afbaa8cb410', '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'История' ),
        ( '7290c5f7-1000-4f44-a5f0-6a7cf8a8efab', '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Литература' ),
        ( '73ac366d-c7c2-4480-9513-28c17967db1a', '52a719bf-6f1f-48ac-9e1f-4532cfc70d96', N'Древне-шумерский диалект иврита' )

    insert into @tmpMarks
    values
        ( NewId(), '30945b68-a6ef-4da8-9a35-d3b2845e7de3', 98 ),
        ( NewId(), '30945b68-a6ef-4da8-9a35-d3b2845e7de3', 87 ),
        ( NewId(), '30945b68-a6ef-4da8-9a35-d3b2845e7de3', 76 ),

        ( NewId(), '7881f090-ccd6-4fb9-a1e0-ff4ff5c18450', 89 ),
        ( NewId(), '7881f090-ccd6-4fb9-a1e0-ff4ff5c18450', 78 ),
        ( NewId(), '7881f090-ccd6-4fb9-a1e0-ff4ff5c18450', 67 ),

        ( NewId(), '92bbefd1-9fec-4dc7-bb58-986eadb105c8', 79 ),
        ( NewId(), '92bbefd1-9fec-4dc7-bb58-986eadb105c8', 68 ),
        ( NewId(), '92bbefd1-9fec-4dc7-bb58-986eadb105c8', 75 ),
        ----------
        ( NewId(), '923a2f0c-c5c7-4394-847c-c5028fe14711', 198 ),
        ( NewId(), '923a2f0c-c5c7-4394-847c-c5028fe14711', 187 ),
        ( NewId(), '923a2f0c-c5c7-4394-847c-c5028fe14711', 176 ),

        ( NewId(), 'ace50388-eb05-4c46-82a9-5836cf0c988c', 189 ),
        ( NewId(), 'ace50388-eb05-4c46-82a9-5836cf0c988c', 178 ),
        ( NewId(), 'ace50388-eb05-4c46-82a9-5836cf0c988c', 167 ),
        ----------
        ( NewId(), '53ea69fb-6cc4-4a6f-82c2-0afbaa8cb410', 8 ),
        ( NewId(), '53ea69fb-6cc4-4a6f-82c2-0afbaa8cb410', 7 ),
        ( NewId(), '53ea69fb-6cc4-4a6f-82c2-0afbaa8cb410', 6 ),

        ( NewId(), '7290c5f7-1000-4f44-a5f0-6a7cf8a8efab', 9 ),
        ( NewId(), '7290c5f7-1000-4f44-a5f0-6a7cf8a8efab', 8 ),
        ( NewId(), '7290c5f7-1000-4f44-a5f0-6a7cf8a8efab', 7 ),

        ( NewId(), '73ac366d-c7c2-4480-9513-28c17967db1a', 9 ),
        ( NewId(), '73ac366d-c7c2-4480-9513-28c17967db1a', 8 ),
        ( NewId(), '73ac366d-c7c2-4480-9513-28c17967db1a', 5 )

--------------------------------------------------

    select * from @tmpPersons
    select * from @tmpBooks
    select * from @tmpCourses
    select * from @tmpMarks

end
go

1
Bu yaklaşımın şimdiye kadar neden ilgi veya yorum almadığını bilmiyorum ama çok ilginç ve mantıksal olarak yapılandırılmış buluyorum. Paylaşım için teşekkürler. Bu yaklaşımı tablo değerli işlevlere ve hatta SQL dizelerine de uygulayabileceğinizi düşünüyorum - yalnızca komut türünde farklılık gösterirler. Yalnızca bazı uzantılar / aşırı yüklemeler ve bu, tüm yaygın sorgu türleri için çalışmalıdır.
Grimm

Bu doğru okuduğumdan emin olmak için, bu, kullanıcının prosedürün sonuçları tam olarak hangi tür sırayla döndüreceğini bilmesini gerektirir, doğru mu? Örneğin <Kitap> Ekle ve <Ders> İçer'i değiştirirseniz, bu?
cubesnyc

@cubesnyc Fırlatıp atmadığını hatırlamıyorum, ama evet, kullanıcı sırayı bilmeli
Sam Sch

2

Bu konuyla ilgili çözümümü paylaşmak ve kullandığım yaklaşımla ilgili herhangi birinin yapıcı geri bildirimi olup olmadığını görmek istedim.

Üzerinde çalıştığım projede önce açıklamam gereken birkaç gereksinim var:

  1. POCO'larımı olabildiğince temiz tutmalıyım çünkü bu sınıflar bir API sarmalayıcısında herkese açık olarak paylaşılacak.
  2. POCO'larım, yukarıdaki gereksinim nedeniyle ayrı bir Sınıf Kitaplığında
  3. Verilere bağlı olarak değişecek birden fazla nesne hiyerarşi seviyesi olacak (bu yüzden bir Genel Tip Eşleştiricisi kullanamam veya olası tüm olasılıkları karşılamak için tonlarca yazmak zorunda kalırım)

Peki, ne yapmış SQL 2 işlemek için elde etmektir - aşağıdaki gibi orijinal satırda bir sütun olarak bir tek JSON dizesi dönerek inci Seviye hiyerarşi ( ayıklanmış olarak diğer sütunlar / özellikler vb göstermek için ):

Id  AttributeJson
4   [{Id:1,Name:"ATT-NAME",Value:"ATT-VALUE-1"}]

Ardından, POCO'larım aşağıdaki gibi oluşturuldu:

public abstract class BaseEntity
{
    [KeyAttribute]
    public int Id { get; set; }
}

public class Client : BaseEntity
{
    public List<ClientAttribute> Attributes{ get; set; }
}
public class ClientAttribute : BaseEntity
{
    public string Name { get; set; }
    public string Value { get; set; }
}

POCO'nun BaseEntity'den miras aldığı yer. (İstemci nesnesinin "Nitelikler" özelliğinde gösterildiği gibi oldukça basit, tek seviyeli bir hiyerarşi seçtiğimi göstermek için.)

Daha sonra Veri Katmanımda POCO'dan devralan aşağıdaki "Veri Sınıfı" var Client .

internal class dataClient : Client
{
    public string AttributeJson
    {
        set
        {
            Attributes = value.FromJson<List<ClientAttribute>>();
        }
    }
}

Yukarıda görebileceğiniz gibi, AttributeJsonSQL'in dataClient sınıfındaki özelliğe eşlenen "AttributeJson" adlı bir sütun döndürmesidir . Bu yalnızca JSON'u Attributesmiras alınan Clientsınıftaki özelliğe serisini kaldıran bir ayarlayıcıya sahiptir . DataClient Sınıfıinternal Veri Erişim Katmanı içindir ve ClientProvider(benim veri fabrikam) orijinal İstemci POCO'yu çağıran Uygulama / Kitaplığa şu şekilde döndürür:

var clients = _conn.Get<dataClient>();
return clients.OfType<Client>().ToList();

Dapper.Contrib kullandığımı ve yeni bir Get<T> döndüren YöntemIEnumerable<T>

Bu çözümde dikkat edilmesi gereken birkaç nokta var:

  1. JSON serileştirme ile bariz bir performans değiş tokuşu var - bunu 2 alt ile 1050 satırla karşılaştırdım List<T> tokuşu var - bunu, her biri listede 2 varlık bulunan ve 279ms'de özellikli - bu da projelerimin ihtiyaçları için kabul edilebilir - bu aynı zamanda Şeylerin SQL tarafında SIFIR optimizasyon, bu yüzden orada birkaç ms tıraş edebilmeliyim.

  2. Bu, gereken her biri için JSON'u oluşturmak için ek SQL sorgularının gerekli olduğu anlamına gelir List<T> özellik , ancak yine de SQL'i çok iyi bildiğim ve dinamikler / yansıma vb. Konusunda o kadar akıcı olmadığım için bu bana uygundur. Kaputun altında neler olduğunu gerçekten anladığım için şeyler üzerinde daha fazla kontrol :-)

Bundan daha iyi bir çözüm olabilir ve eğer varsa düşüncelerinizi duymaktan gerçekten memnun olurum - bu, şu ana kadar bu proje için ihtiyaçlarıma uyan bulduğum çözümdür (bu, yayınlama aşamasında deneysel olmasına rağmen) ).


Bu ilginç. SQL bölümünü paylaşma şansınız var mı?
WhiteRuski
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.