多Mapper创建对象层次结构
我已经玩了一段时间了,因为它看起来很像 记录的帖子/用户示例,但它略有不同,对我不起作用。
假设以下简化设置(联系人有多个电话号码):
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 行的结果集。然后 Dapper 会弹出 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.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);
我只是做错了什么吗?这看起来就像 posts/owner 的例子,只不过我是从父级到子级而不是从子级到父级。
I've been playing around with this for a bit, because it seems like it feels a lot like the documented posts/users example, but its slightly different and isn't working for me.
Assuming the following simplified setup (a contact has multiple phone numbers):
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; }
}
I'd love to end up with something that returns a Contact with multiple Phone objects. That way, if I had 2 contacts, with 2 phones each, my SQL would return a join of those as a result set with 4 total rows. Then Dapper would pop out 2 contact objects with two phones each.
Here is the SQL in the stored procedure:
SELECT *
FROM Contacts
LEFT OUTER JOIN Phones ON Phones.ReferenceId=Contacts.ReferenceId
WHERE clientid=1
I tried this, but ended up with 4 Tuples (which is OK, but not what I was hoping for... it just means I still have to re-normalize the result):
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);
and when I try another method (below), I get an exception of "Unable to cast object of type 'System.Int32' to type '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);
Am I just doing something wrong? It seems just like the posts/owner example, except that I'm going from the parent to the child instead of the child to the parent.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
你没有做错什么,只是 API 不是这样设计的。所有
Query
API 将始终为每个数据库行返回一个对象。所以,这对很多人来说都很有效 ->一个方向,但不太好 ->很多多图。
这里有两个问题:
如果我们引入一个适用于您的查询的内置映射器,我们将被期望“丢弃”重复数据。 (Contacts.* 在您的查询中重复)
如果我们将其设计为与一个 ->很多对,我们将需要某种身份映射。这增加了复杂性。
举个例子,如果您只需要提取有限数量的记录,则该查询是有效的,如果您将其推至一百万条记录,则会变得更加棘手,因为您需要流式传输并且无法将所有内容加载到内存中:
您可以做的是扩展 GridReader 以允许重新映射:
假设您扩展 GridReader 并使用映射器:
因为这有点棘手和复杂,但需要注意。我不倾向于将其纳入核心。
You are doing nothing wrong, it is just not the way the API was designed. All the
Query
APIs will always return an object per database row.So, this works well on the many -> one direction, but less well for the one -> many multi-map.
There are 2 issues here:
If we introduce a built-in mapper that works with your query, we would be expected to "discard" duplicate data. (Contacts.* is duplicated in your query)
If we design it to work with a one -> many pair, we will need some sort of identity map. Which adds complexity.
Take for example this query which is efficient if you just need to pull a limited number of records, if you push this up to a million stuff get trickier, cause you need to stream and can not load everything into memory:
What you could do is extend the
GridReader
to allow for the remapping:Assuming you extend your GridReader and with a mapper:
Since this is a bit tricky and complex, with caveats. I am not leaning towards including this in core.
仅供参考 - 我通过执行以下操作得到了 Sam 的答案:
首先,我添加了一个名为“Extensions.cs”的类文件。我必须在两个地方将“this”关键字更改为“reader”:
其次,我添加了以下方法,修改了最后一个参数:
FYI - I got Sam's answer working by doing the following:
First, I added a class file called "Extensions.cs". I had to change the "this" keyword to "reader" in two places:
Second, I added the following method, modifying the last parameter:
查看 https://www.tritac.com/blog/dappernet-by-example/
你可以这样做:
我从 dapper.net 测试中得到了这个:
Check out https://www.tritac.com/blog/dappernet-by-example/
You could do something like this:
I got this from the dapper.net tests: https://code.google.com/p/dapper-dot-net/source/browse/Tests/Tests.cs#1343
多结果集支持
在您的情况下,拥有多结果集查询会更好(也更容易)。这仅仅意味着您应该编写两个选择语句:
这样您的对象将是唯一的并且不会重复。
Multi result set support
In your case it would be much better (and easier as well) to have a multi resultset query. This simply means that you should write two select statements:
This way your objects would be unique and wouldn't duplicate.
这是一个非常易于使用的可重复使用的解决方案。这是对安德鲁斯答案的轻微修改。
用法示例
Here's a reusable solution that is pretty easy to use. It is a slight modification of Andrews answer.
Example usage
基于 Sam Saffron(和 Mike Gleason)的方法,这里有一个允许多个子级和多个级别的解决方案。
然后你可以让它在函数之外读取。
然后可以使用相同的父对象为下一个子对象再次调用映射函数。您还可以独立于地图在父或子读取语句上实现分割功能。
这是一个“单到N”的附加扩展方法
Based on Sam Saffron's (and Mike Gleason's) approach, here is a solution which will allow for multiple children and multiple levels.
Then you can have it read outside of the function.
The map function can then be called again for the next child object using the same parent object. You can also implement splits on the parent or child read statements independently of the map function.
Here is a 'single to N' additional extension method
一旦我们决定将 DataAccessLayer 移至存储过程,这些过程通常会返回多个链接结果(示例如下)。
嗯,我的方法几乎相同,但可能更舒服一些。
您的代码可能如下所示:
Let's break it down...
扩展:
映射器:
就这些,但如果您想进行快速测试,这里有适合您的模型和过程:
模型:
SP :
Once we decided to move our DataAccessLayer to stored procedures, and these procedures are often return multiple linked results (example below).
Well, my approach is almost the same, but maybe little bit more comfortable.
This is how your code may look like:
Let's break it down...
Extension:
Mapper:
That's all, but if you wanna do quick test, here is models and procedure for you:
Models:
SP:
我想分享我对此问题的解决方案,看看是否有人对我使用的方法有任何建设性的反馈?
我正在处理的项目中有一些要求,我需要首先解释一下:
因此,我所做的就是通过返回单个 JSON 字符串作为原始行上的列,让 SQL 处理第 2 级 - 第 n 级层次结构,如下所示(删除其他列/属性等以说明):
然后,我的 POCO 的构建如下:
POCO 继承自 BaseEntity。 (为了说明,我选择了一个相当简单的单级层次结构,如客户端对象的“属性”属性所示。)
然后,我在数据层中拥有以下继承的“数据类”来自 POCO
客户端
。正如您在上面看到的,发生的情况是 SQL 返回一个名为“AttributeJson”的列,该列映射到 dataClient 类中的属性
AttributeJson
。它只有一个 setter,它将 JSON 反序列化为继承的Client
类上的Attributes
属性。 dataClient 类是数据访问层的内部
,而ClientProvider
(我的数据工厂)将原始 Client POCO 返回到调用应用程序/库,如下所示:请注意,我'我使用 Dapper.Contrib 并添加了一个新的
Get
方法,该方法返回IEnumerable
此解决方案需要注意以下几点:
JSON 序列化存在明显的性能权衡 - 我已针对具有 2 个子
List
属性的 1050 行进行了基准测试,每个属性在列表中都有 2 个实体,其时钟频率为279ms - 这对于我的项目需求来说是可以接受的 - 这也是 SQL 方面的零优化,所以我应该能够在那里节省一些毫秒。这确实意味着需要额外的 SQL 查询来为每个必需的
List
属性构建 JSON,但是这又适合我,因为我非常了解 SQL,但不太流利动态/反射等..所以这样我觉得我对事情有更多的控制权,因为我实际上了解幕后发生的事情:-)很可能有比这个更好的解决方案,如果有的话,我真的很感激听到你的想法 - 这只是我想出的解决方案,到目前为止符合我的需求项目(虽然这是实验阶段的发布)。
I wanted to share my solution to this issue and see if anyone has any constructive feedback on the approach I've used?
I have a few requirements in the project I'm working on which I need to explain first up:
So, what I have done is to get SQL to handle the 2nd - nth Level heirarchy by returning a Single JSON string as a column on the original row as follows (stripped out the other columns / properties etc to illustrate):
Then, my POCO's are built up like the below :
Where the POCO's inherit from BaseEntity. (To illustrate I've chosen a fairly simple, single level heirarchy as shown by the "Attributes" property of the client object. )
I then have in my Data Layer the following "Data Class" which inherits from the POCO
Client
.As you can see above, whats happening is that SQL is returning a column called "AttributeJson" which is mapped to the property
AttributeJson
in the dataClient class. This has only a setter which deserialises the JSON to theAttributes
property on the inheritedClient
class. The dataClient Class isinternal
to the Data Access Layer and theClientProvider
(my data factory) returns the original Client POCO to the calling App / Library like so :Note that I'm using Dapper.Contrib and have added a new
Get<T>
Method that returns anIEnumerable<T>
There are a couple things to note with this solution:
There's an obvious performance trade off with the JSON serialisation - I've benchmarked this against 1050 rows with 2 sub
List<T>
properties, each with 2 entities in the list and it clocks in at 279ms - which is acceptable for my projects needs - this is also with ZERO optimisation on the SQL side of things so I should be able to shave a few ms there.It does mean additional SQL queries are required to build up the JSON for each required
List<T>
property, but again, this suits me as I know SQL pretty well and am not so fluent on dynamics / reflection etc.. so this way I feel like I have more control over things as I actually understand whats happening under the hood :-)There may well be a better solution than this one and if there is I would really appreciate hearing your thoughts - this is just the solution I came up with that so far fits my needs for this project (although this is experimental at the stage of posting).
摘自learndapper.com - 使用 Dapper 的关系:SplitOn
给定这些类
你可以使用这个
Taken from learndapper.com - Using Relationships With Dapper: SplitOn
Given these classes
You can use this