ऑब्जेक्ट पदानुक्रम बनाने के लिए मल्टी-मैपर


82

मैं इसके साथ थोड़ी देर के लिए खेल रहा हूं, क्योंकि ऐसा लगता है कि यह बहुत डाक्यूमेंटेड पोस्ट्स / यूजर्स के उदाहरण जैसा लगता है , लेकिन यह थोड़ा अलग है और मेरे लिए काम नहीं कर रहा है।

निम्न सरलीकृत सेटअप मान लें (एक संपर्क में कई फ़ोन नंबर हैं):

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

मैं एक ऐसी चीज़ के साथ अंत करना पसंद करूँगा जो कई फोन ऑब्जेक्ट्स के साथ एक संपर्क लौटाती है। इस तरह, अगर मेरे पास 2 संपर्क थे, प्रत्येक में 2 फोन थे, तो मेरी SQL 4 कुल पंक्तियों के साथ सेट परिणाम के रूप में उन में से एक को वापस लौटा देगी। फिर डैपर दो फोन के साथ 2 कॉन्टैक्ट ऑब्जेक्ट्स को पॉप आउट करेगा।

यहाँ संग्रहीत कार्यविधि में SQL है:

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

मैंने यह कोशिश की, लेकिन 4 ट्यूपल्स के साथ समाप्त हुआ (जो ठीक है, लेकिन वह नहीं जो मैं उम्मीद कर रहा था ... इसका मतलब है कि मुझे अभी भी परिणाम को फिर से सामान्य करना होगा):

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

और जब मैं एक अन्य विधि (नीचे) आज़माता हूं, तो मुझे "System.nt। टाइप करने के लिए 'System.nt32' टाइप करने के लिए 'System.Collections.Generic.IEnumerable`1 [Phone]' टाइप करने में असमर्थ होता है।"

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

क्या मैं सिर्फ कुछ गलत कर रहा हूं? यह पदों / मालिक उदाहरण की तरह लगता है, सिवाय इसके कि मैं माता-पिता से बच्चे के बजाय बच्चे से माता-पिता के पास जा रहा हूं।

अग्रिम में धन्यवाद

जवाबों:


69

आप कुछ भी गलत नहीं कर रहे हैं, यह सिर्फ उस तरह से नहीं है जिस तरह से एपीआई को डिजाइन किया गया था। सभी QueryAPI हमेशा डेटाबेस पंक्ति के अनुसार एक ऑब्जेक्ट लौटाएंगे।

तो, यह कई पर अच्छा काम करता है -> एक दिशा, लेकिन एक के लिए कम अच्छी तरह -> कई बहु-मानचित्र।

यहाँ 2 मुद्दे हैं:

  1. यदि हम एक बिल्ट-इन मैपर पेश करते हैं जो आपकी क्वेरी के साथ काम करता है, तो हमें डुप्लिकेट डेटा को "त्याग" करने की उम्मीद होगी। (संपर्क। * आपकी क्वेरी में डुप्लिकेट है)

  2. यदि हम इसे एक -> कई जोड़ी के साथ काम करने के लिए डिज़ाइन करते हैं, तो हमें किसी प्रकार के पहचान मानचित्र की आवश्यकता होगी। जो जटिलता जोड़ता है।


उदाहरण के लिए इस क्वेरी को लें, जो आपको केवल रिकॉर्ड की एक सीमित संख्या को खींचने की जरूरत है, अगर यह कुशल है, तो यदि आप इसे एक लाख तक बढ़ाते हैं तो पेचीदा हो जाता है, जिसके कारण आपको स्ट्रीम करने की आवश्यकता होती है और यह सब कुछ मेमोरी में लोड नहीं कर सकता है:

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

GridReaderरीमैपिंग की अनुमति देने के लिए आप क्या कर सकते हैं :

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

मान लें कि आप अपने GridReader का विस्तार करते हैं और एक मैपर के साथ:

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

चूंकि यह कैवियट्स के साथ थोड़ा मुश्किल और जटिल है। मैं कोर में इसे शामिल करने की ओर नहीं झुक रहा हूं।


बहुत ही शांत। इस चीज़ में बहुत अधिक शक्ति है ... मुझे लगता है कि इसका उपयोग करने के तरीके के बारे में पता चल रहा है। मैं अपने प्रश्नों के पेलोड में देखूंगा और देखूंगा कि परिणाम कितने बड़े हैं और देखते हैं कि क्या हम एक से अधिक प्रश्न कर सकते हैं और उन्हें एक साथ कर सकते हैं।
जोरिन

@ जॉरीन, आपका अन्य विकल्प कई कनेक्शनों को ऑर्केस्ट्रेट करना और परिणाम बुनना होगा। थोड़ा मुश्किल है।
सैम केसर

1
मैं भी अगर (childMap.TryGetvalue (..)) के बाद एक और जोड़ देगा ताकि बाल संग्रह डिफ़ॉल्ट रूप से रिक्त संग्रह के लिए शुरू हो जाए, बजाय NULL के बजाय यदि बाल आइटम नहीं हैं। इस तरह: और {addChildren (आइटम, नया TChild [] {}); }
मेरियस

1
@SamSaffron मुझे डैपर से प्यार है। धन्यवाद। मेरे पास हालांकि एक सवाल है। SQL प्रश्नों में एक-से-कई एक सामान्य घटना है। डिज़ाइन में, कार्यान्वयनकर्ता के उपयोग के लिए आपके पास क्या ध्यान था? मैं इसे Dapper तरीके से करना चाहता हूं, लेकिन मैं इस समय SQL तरीके से हूं। मैं एसक्यूएल से आने के बारे में कैसे सोचता हूं, जहां वन साइड आमतौर पर "ड्राइवर" है। डापर में कई पक्ष ऐसा क्यों है? क्या बिंदु है इसलिए हम वस्तु प्राप्त करते हैं और तथ्य के बाद पार्स करते हैं? महान पुस्तकालय के लिए धन्यवाद।
जॉनी

2
सुनिश्चित करें कि आप नौकरी के लिए सही उपकरण का उपयोग कर रहे हैं। यदि आपके पास बड़े पैमाने पर डेटाबेस प्रदर्शन की आवश्यकताएं नहीं हैं, या आपने अपने सिस्टम को बेंचमार्क नहीं किया है, तो आपने डैपर का उपयोग करके अपने जीवन के घंटों या शायद दिनों को बर्बाद कर दिया है।
अलुआन हदाद

32

FYI करें - मुझे सैम का जवाब निम्नलिखित काम करके मिला:

सबसे पहले, मैंने "Extensions.cs" नामक एक वर्ग फ़ाइल जोड़ी। मुझे "रीडर" को दो स्थानों पर "रीडर" में बदलना था:

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

दूसरा, मैंने अंतिम पैरामीटर को संशोधित करते हुए निम्नलिखित विधि जोड़ी:

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/ आप कुछ इस तरह कर सकता है:

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;

मुझे यह dapper.net परीक्षणों से मिला: https://code.google.com/p/dapper-dot-net/source/browse/Tests/Tests.cs#1343


2
वाह! मेरे लिए, मुझे यह सबसे आसान उपाय लगा। दी गई, एक-> कई के लिए, (दो तालिकाओं को मानते हुए), मैं दोहरे चयन के साथ जाऊंगा। हालाँकि, मेरे मामले में, मेरे पास एक-> एक-> कई हैं और यह बहुत अच्छा काम करता है। अब, यह बहुत अधिक अनावश्यक डेटा वापस लाता है लेकिन मेरे मामले के लिए, यह अतिरेक अपेक्षाकृत छोटा है - 10 पंक्तियाँ।
कोड ५

यह दो स्तरों के लिए अच्छी तरह से काम करता है, लेकिन अधिक होने पर यह मुश्किल हो जाता है।
समीर अगुइर

1
यदि कोई बाल डेटा नहीं है, तो (ए) कोड को = null के साथ बुलाया जाएगा, और खातों में रिक्त प्रविष्टि के बजाय रिक्त प्रविष्टि वाली सूची होगी। आपको "shop (। = Null)" को "shop.Accounts.Add (a)" से पहले जोड़ने की जरूरत है
Etienne Charland

12

बहु परिणाम सेट समर्थन

आपके मामले में यह बहु परिणामसेट क्वेरी के लिए बेहतर (और आसान होने के साथ-साथ) बेहतर होगा। इसका सीधा सा मतलब है कि आपको दो चुनिंदा कथन लिखने चाहिए:

  1. वह जो संपर्क लौटाता है
  2. और एक जो उनके फोन नंबर वापस करता है

इस तरह से आपकी वस्तुएँ अद्वितीय होंगी और डुप्लिकेट नहीं होंगी।


1
जबकि अन्य जवाब अपने तरीके से सुरुचिपूर्ण हो सकते हैं, मैं इसे पसंद करना चाहता हूं क्योंकि कोड के बारे में तर्क करना आसान है। मैं एक पदानुक्रम का निर्माण कर सकता हूँ जो कुछ चुनिंदा बयानों के साथ कुछ स्तर गहरा है, और लगभग 30 पंक्तियों की फ़ॉरच / लिन्क कोड। यह बड़े पैमाने पर परिणाम सेट के साथ टूट सकता है, लेकिन सौभाग्य से मुझे वह समस्या नहीं है (अभी तक)।
सैम स्टिरिएन

10

यहाँ एक पुन: प्रयोज्य समाधान है जिसका उपयोग करना बहुत आसान है। यह एंड्रयूज जवाब का एक मामूली संशोधन है ।

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

उदाहरण उपयोग

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

सैम सैफ्रन (और माइक ग्लीसन) के दृष्टिकोण के आधार पर, यहां एक समाधान है जो कई बच्चों और कई स्तरों के लिए अनुमति देगा।

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

तब आप इसे फ़ंक्शन के बाहर पढ़ सकते हैं।

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

मैप फ़ंक्शन को फिर उसी पेरेंट ऑब्जेक्ट का उपयोग करके अगली चाइल्ड ऑब्जेक्ट के लिए फिर से कॉल किया जा सकता है। आप माता-पिता या बच्चे द्वारा दिए गए विभाजन को भी लागू कर सकते हैं , नक्शे के कार्य के स्वतंत्र रूप से कथन पढ़ सकते हैं।

यहाँ एक 'सिंगल टू एन' अतिरिक्त विस्तार विधि है

    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
इसके लिए धन्यवाद - महान समाधान। यदि यह कथन हटा दिया जाता है ताकि कोई बच्चों पर AddChilder न लगाने के बजाय, कॉलिंग फ़ंक्शन नल को संभाल सके। इस तरह मैं खाली सूचियों को जोड़ सकता हूं, जिनके साथ काम करना बहुत आसान है।
म्लादेन मिहाजलोविक

1
यह एक शानदार उपाय है। मेरे पास "गतिशील खोज" के साथ कुछ मुद्दे थे। इसे इस संपर्क के साथ हल किया जा सकता है। बहु। MapChild <संपर्क, फोन, int> (/ * यहाँ ऊपर जैसा ही कोड * /
granadaCoder

4

एक बार जब हम अपने डेटाअलेयर को संग्रहीत प्रक्रियाओं में स्थानांतरित करने का निर्णय लेते हैं, और ये प्रक्रिया अक्सर कई जुड़े हुए परिणाम (उदाहरण के लिए नीचे) लौटाते हैं।

ठीक है, मेरा दृष्टिकोण लगभग समान है, लेकिन शायद थोड़ा अधिक आरामदायक है।

इस तरह आपका कोड दिख सकता है:

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


चलो इसे तोड़ दो ...

एक्सटेंशन:

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

मैपर:

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

यह सब है, लेकिन अगर आप त्वरित परीक्षण करना चाहते हैं, तो यहां आपके लिए मॉडल और प्रक्रिया है:

मॉडल:

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

सपा:

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
मुझे नहीं पता कि इस दृष्टिकोण पर अब तक ध्यान या टिप्पणी क्यों नहीं हुई है, लेकिन मुझे यह बहुत दिलचस्प और तार्किक रूप से संरचित लगता है। साझा करने के लिए धन्यवाद। मुझे लगता है कि आप इस दृष्टिकोण को टेबल-मूल्यवान कार्यों या यहां तक ​​कि एसक्यूएल स्ट्रिंग्स पर भी लागू कर सकते हैं - वे केवल कमांड प्रकार में भिन्न हैं। बस कुछ एक्सटेंशन / अधिभार और यह सभी सामान्य क्वेरी प्रकारों के लिए काम करना चाहिए।
ग्रिम

यह सुनिश्चित करने के लिए कि यह सही पढ़ा जा रहा है, इसके लिए उपयोगकर्ता को यह जानना आवश्यक है कि प्रक्रिया किस प्रकार के क्रम में परिणाम लौटाएगी, क्या यह सही है? यदि आपने उदाहरण के लिए <बुक> और शामिल करें <कोर्स> शामिल किया है, तो यह फेंक देगा?
क्यूब्सनिक

@ क्यूब्सनिच मुझे याद नहीं है अगर यह फेंकता है, लेकिन हां, उपयोगकर्ता को ऑर्डर पता होना चाहिए
सैम

2

मैं इस मुद्दे पर अपना समाधान साझा करना चाहता था और देखना चाहता था कि किसी ने मेरे द्वारा उपयोग किए गए दृष्टिकोण पर कोई रचनात्मक प्रतिक्रिया दी है?

मैं जिस प्रोजेक्ट पर काम कर रहा हूं, उसमें मेरी कुछ आवश्यकताएँ हैं, जिन्हें मुझे पहले समझाना होगा:

  1. मुझे अपना पोक्सो जितना संभव हो उतना साफ रखना होगा क्योंकि इन वर्गों को सार्वजनिक रूप से एक एपीआई आवरण में साझा किया जाएगा।
  2. उपरोक्त आवश्यकता के कारण मेरा POCO एक अलग श्रेणी की लाइब्रेरी में है
  3. वहाँ कई वस्तु पदानुक्रम स्तर होने जा रहे हैं जो डेटा के आधार पर अलग-अलग होंगे (इसलिए मैं एक सामान्य प्रकार मैपर का उपयोग नहीं कर सकता हूं या मुझे सभी संभावित घटनाओं को पूरा करने के लिए उन्हें टन लिखना होगा)

तो, क्या मैंने किया है एसक्यूएल 2 को संभालने के लिए प्राप्त करने के लिए है - मूल पंक्ति के रूप में इस प्रकार है पर एक स्तंभ के रूप में एक एकल JSON स्ट्रिंग वापस लौट कर वें स्तर पदानुक्रम ( बाहर छीन अन्य स्तंभों / गुण आदि वर्णन करने के लिए ):

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

फिर, मेरे पोको नीचे की तरह बने हैं:

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

जहां POEO का वारिस बेसइंटिटी से है। (चित्रण करने के लिए मैंने क्लाइंट ऑब्जेक्ट की "गुण" गुण द्वारा दिखाए गए अनुसार काफी सरल, एकल स्तर की उत्तराधिकारिणी को चुना है।)

मैं फिर अपने डेटा लेयर में निम्नलिखित "डेटा क्लास" रखता हूं जो कि POCO से विरासत में मिला है Client

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

जैसा कि आप ऊपर देख सकते हैं, व्हाट्सएप हो रहा है कि एसक्यूएल "एट्रिब्यूटजोन" नामक एक कॉलम लौटा रहा है जो AttributeJsonडेटाक्लायंट क्लास में संपत्ति के लिए मैप किया गया है । इसके पास केवल एक सेटर है जो Attributesविरासत में दिए गए Clientवर्ग पर संपत्ति के लिए JSON का विवरण देता है । DataClient Class internalडेटा एक्सेस लेयर के लिए है और ClientProvider(मेरा डेटा फ़ैक्टरी) कॉलिंग ऐप / लाइब्रेरी के लिए मूल ग्राहक POCO लौटाता है:

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

ध्यान दें कि मैं Dapper.Contrib का उपयोग कर रहा हूं और एक नया Get<T>तरीका जोड़ा है जो रिटर्न देता हैIEnumerable<T>

इस समाधान के साथ ध्यान देने योग्य कुछ बातें हैं:

  1. JSON क्रमांकन के साथ एक स्पष्ट प्रदर्शन व्यापार है - मैंने इसे 2 उप List<T>गुणों के साथ 1050 पंक्तियों के खिलाफ बेंचमार्क किया है , प्रत्येक सूची में 2 संस्थाओं के साथ है और यह 279ms में देखता है - जो मेरी परियोजनाओं की जरूरतों के लिए स्वीकार्य है - यह भी साथ है चीजों के एसक्यूएल पक्ष पर शून्य अनुकूलन इसलिए मुझे वहां कुछ एमएस शेव करने में सक्षम होना चाहिए।

  2. इसका मतलब यह है कि प्रत्येक आवश्यक List<T>संपत्ति के लिए JSON का निर्माण करने के लिए अतिरिक्त SQL प्रश्नों की आवश्यकता होती है, लेकिन फिर से, यह मुझे सूट करता है क्योंकि मैं SQL को बहुत अच्छी तरह से जानता हूं और गतिशीलता / प्रतिबिंब आदि पर इतना धाराप्रवाह नहीं हूं .. इसलिए इस तरह से मुझे लगता है जैसे मेरे पास है चीजों पर अधिक नियंत्रण जैसा कि मैं वास्तव में समझता हूं कि हुड के नीचे क्या हो रहा है :-)

इस एक से बेहतर समाधान हो सकता है और अगर वहाँ है तो मैं वास्तव में आपके विचारों को सुनने की सराहना करूंगा - यह सिर्फ समाधान है जो मैं इस परियोजना के लिए मेरी आवश्यकताओं के अनुरूप आता हूं (हालांकि यह पोस्टिंग के स्तर पर प्रयोगात्मक है )।


यह दिलचस्प है। कोई भी मौका जो आप SQL हिस्से को साझा कर सकते हैं?
व्हाइटरुस्की
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.