多映射器创建对象层次结构


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

我很乐意以返回带有多个Phone对象的Contact的结尾。这样,如果我有2个联系人,每个联系人有2部手机,那么我的SQL将返回这些联系人的联接,结果集共有4行。然后Dapper弹出两个带有两个电话的联系人对象。

这是存储过程中的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.Int32'的对象转换为类型为'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);

我是在做错什么吗?看起来就像帖子/所有者的示例一样,只是我要从父级转到子级,而不是从子级转到父级。

提前致谢

Answers:


69

您做错了什么,这不是API设计的方式。所有QueryAPI都会每个数据库行中始终返回一个对象。

因此,这在多个->一个方向上效果很好,但对于一个->多个多图效果不佳。

这里有两个问题:

  1. 如果我们引入了可与您的查询配合使用的内置映射器,则我们将“丢弃”重复的数据。(Contacts。*在查询中重复)

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

因为这有点棘手和复杂,但有一些警告。我不打算将其包含在核心中。


很酷。这个东西具有相当大的力量...我想它已经习惯了如何使用它。我将研究查询的有效负载,看看结果集有多大,并看看我们是否有能力负担多个查询并将它们映射在一起。
Jorin 2011年

@Jorin,您的另一个选择是协调多个连接并编织结果。这有点棘手。
山姆藏红花

1
我还将在if(childMap.TryGetvalue(..))之后添加一个else,这样,如果没有子项,则默认将子集合初始化为空集合,而不是NULL。像这样:else {addChildren(item,new TChild [] {}); }
Marius 2012年

1
@SamSaffron我爱Dapper。谢谢。我确实有一个问题。一对多是SQL查询中的常见现象。在设计中,您想到实现者要使用什么?我想以Dapper方式进行操作,但目前我处于SQL方式。我如何考虑来自SQL的问题,而SQL通常是“一侧”的“驱动程序”。为什么在Dapper中如此多?这样做是为了让我们得到对象并在事实之后进行解析吗?感谢图书馆。
约翰尼

2
确保您使用正确的工具进行作业。如果您对数据库的性能没有很高的要求,或者没有对系统进行基准测试,则使用Dapper可能会浪费数小时甚至数天的时间。
Aluan Haddad

32

仅供参考-通过执行以下操作,我得到了Sam的答案:

首先,我添加了一个名为“ Extensions.cs”的类文件。我不得不在两个地方将“ this”关键字更改为“ reader”:

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
哇!对我来说,我发现这是最简单的解决方案。当然,对于一个>很多(假设有两个表)的情况,我会选择双重选择。但是,就我而言,我有一个-> one->很多,这很好用。现在,它确实带回了很多冗余数据,但就我而言,这种冗余相对较小-最多10行。
代码5

这对于两个级别来说效果很好,但是当您拥有更多级别时,它会变得棘手。
萨米尔·阿吉亚

1
如果没有子数据,则将使用a = null调用(s,a)代码,并且Accounts将包含具有空条目的列表,而不是空列表。您需要在“ shop.Accounts.Add(a)”之前添加“ if(a!= null)”
Etienne Charland

12

多结果集支持

在您的情况下,进行多结果集查询会更好(也更容易)。这只是意味着您应该编写两个select语句:

  1. 一个返回联系人
  2. 然后传回他们的电话号码

这样,您的对象将是唯一的,并且不会重复。


1
尽管其他答案以他们自己的方式可能很优雅,但我倾向于喜欢这个答案,因为代码更容易推论。我可以用几个select语句和大约30行的foreach / linq代码来构建一个多层的层次结构。这可能会因大量的结果集而失效,但是幸运的是我还没有这个问题。
山姆·斯托伊,2015年

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

基于Sam Saffron(和Mike Gleason)的方法,这是一个允许多个孩子和多个级别的解决方案。

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

然后可以使用相同的父对象再次为下一个子对象调用map函数。您也可以独立于map函数在父或子read语句上实现拆分

这是“单对N”附加扩展方法

    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
谢谢你-很好的解决方案。删除了if语句,以便调用函数可以处理空值,而不是不对任何子级不调用addChilder。这样,我可以添加更易于使用的空列表。
Mladen Mihajlovic

1
这是一个很棒的解决方案。我在“动态查找”方面遇到了一些问题。可以使用以下contactList = multi.MapChild <Contact,Phone,int>(/ *与上面相同的代码在这里* /
granadaCoder

4

一旦我们决定将DataAccessLayer移到存储过程中,这些过程通常会返回多个链接结果(以下示例)。

好吧,我的方法几乎是相同的,但也许会更舒服一些。

这是您的代码的样子:

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

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
我不知道为什么这种方法到目前为止还没有引起注意或评论,但是我发现它非常有趣并且逻辑合理。感谢分享。我认为您可以将这种方法应用于表值函数甚至SQL字符串-它们只是命令类型不同。只是一些扩展/重载,这应该适用于所有常见查询类型。
格林

为了确保即时阅读权限,这要求用户确切知道过程将以哪种类型的顺序返回结果,对吗?例如,如果交换了Include <Book>和Include <Course>,这会抛出吗?
cubesnyc

@cubesnyc我不记得它是否抛出,但是是的,用户必须知道命令
Sam Sch

2

我想分享我对这个问题的解决方案,看看是否有人对我使用的方法有建设性的反馈?

我正在处理的项目中有一些需求,我需要首先解释一下:

  1. 我必须保持POCO尽可能整洁,因为这些类将在API包装器中公开共享。
  2. 由于上述要求,我的POCO位于单独的类库中
  3. 将有多个对象层次结构级别,这些级别将根据数据而有所不同(因此,我无法使用通用类型映射器,或者我不得不编写大量的对象层次结构来满足所有可能的情况)

因此,我要做的是通过返回一个JSON字符串作为原始行上的一列,使SQL处理第二级-第n级层次结构,如下所示(去掉了其他列/属性等进行说明):

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

然后,我的POCO如下构建:

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继承自BaseEntity的地方。(为说明起见,我选择了一个相当简单的单级层次结构,如客户端对象的“ Attributes”属性所示。)

然后,在我的数据层中有一个继承自POCO的以下“数据类” Client

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

正如您在上面看到的,发生的事情是SQL返回了一个名为“ AttributeJson”的列,该列映射到AttributeJsondataClient类中的属性。它只有一个setter,可以将JSON反序列化为Attributes继承Client类的属性。dataClient类internal用于数据访问层和ClientProvider(我的数据工厂)将原始的客户端POCO返回到调用的应用程序/库,如下所示:

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

请注意,我正在使用Dapper.Contrib并添加了一个新 Get<T>方法,该方法返回一个IEnumerable<T>

此解决方案有两点需要注意:

  1. 与JSON序列化之间存在明显的性能折衷-我已经针对1050个具有2个子List<T>属性的行进行了基准测试,每个属性具有列表中的2个实体,并且其时钟时间为279ms-这对于我的项目需求是可以接受的-这也SQL方面的零优化,因此我应该能够在其中节省几毫秒的时间。

  2. 这确实意味着需要额外的SQL查询来为每个必需的List<T>属性建立JSON ,但这又很适合我,因为我非常了解SQL,并且对动态/反射等不太熟练。因此,我觉得自己拥有当我真正了解引擎盖下发生的事情时,可以更好地控制事物:-)

可能有比这个更好的解决方案,如果有的话,我真的很感激您的想法-这只是我想出的解决方案,到目前为止已经可以满足我对该项目的需求(尽管这是发布阶段的试验性工作) )。


这是有趣的。您是否有机会共享SQL部分?
WhiteRuski
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.