我最近刚开始研究Dapper。我正在测试它并且能够做基本的CRUD,基本的意思是在这个结构上工作:
public class Product {
public int Id {get;set;}
public string Name {get;set;}
}
现在我正在寻找能够更容易地进行插入和更新的东西,并找到了Dapper.Rainbow。我检查了它,并能够使用它来获取和插入对象,如上所述。我的问题是,当Product
具有导航属性时,我无法在该字段上插入。如果我有这个:
public class Product {
public int Id {get;set;}
public string Name {get;set;}
public ProductCategory Category {get;set;}
}
我将无法做到这一点:
// connection is a valid and opened connection
var db = TestDatabase.Init(connection , 300);
var newId = db.Products.Insert(newProduct);
因为这个原因:
The member Category of type ProductCategory cannot be used as a parameter value
如果我将Category
替换为类型int
(数据库中的数据类型相同),则可以解决此问题。但是,如果我这样做,我将无法使用其类别信息查询产品,而不仅仅是(类别)ID。
因此,如果不使用原始Dapper,如何使用具有导航属性的类进行插入和更新?我希望我可以执行以下操作并告诉Dapper.Rainbow在插入或更新时忽略Category
。
public class Product {
public int Id {get;set;}
public string Name {get;set;}
public ProductCategory Category {get;set;}
public int CategoryId {get;set;} // this will be the same field name in the database
}
这种情况可以通过NHibernate实现,我可以拥有Category
的代理对象并将其分配给Product
并保存它,并且映射完美无缺。但我很乐意使用Dapper,这就是我探索的原因,并希望了解如何做到这一点。
答案 0 :(得分:22)
Dapper.Rainbow
目前的形式无法做到这一点,但my pull request in github使这成为可能。
我很惊讶没有人建议使用Dapper.Contrib
。我知道我问过功能是否在Rainbow中。但我没想到没有人会注意到这句话(特别是粗体文字):
现在我正在寻找能让你更容易做的事情 插入和更新,找到了Dapper.Rainbow。我检查了一下然后 能够使用它来获取和插入对象,如上所述。我的 问题是,当Product具有导航属性时,我无法做到 在该字段上插入。
...并提出一个替代方案,即Dapper库中已有的解决方案。我想我应该更清楚我的问题并明确询问整个Dapper library that is in github中某处是否存在解决方案。因此,在对图书馆进行更多挖掘之后,我发现我的问题得到了支持。
我的项目和Rainbow
一切正常,直到我需要更多内容。我有一些表中有很多字段。如果我只是向Rainbow提供我的对象,那么它将对所有字段进行更新,这并不是一件好事。但这并不能让我迅速跳出船并返回NH。所以在我实施自己的change tracking
之前,我并不想重新发明轮子,特别是如果有人已经做好了工作,我会用Google搜索found this SO thread。该帖子确认我知道 Rainbow
不支持更改跟踪,但还有另一个野兽,它被称为Dapper.Contrib
。所以我开始尝试它。
ProductCategory类型的成员类别不能用作 参数值
我遇到与Rainbow
相同的问题。 Contrib
不支持导航属性!?我开始觉得我在Dapper
浪费我的时间,而且它提供的表现,我非常追求,只会是一厢情愿的想法。直到...
此类位于SqlMapperExtensions.cs
项目中包含的Dapper.Contrib
文件中。我没有找到关于这个类的任何文档,也没有任何评论可以让它容易找到,并大声喊我并说hey I'm the one you're looking for
。如上所述,当我将Rainbow放在一边时,我偶然发现了这一点。
这个课程的用法与我对IgnorePropertyAttribute
的用法相同,它是一个可以装饰你的课程的属性'财产。您应该使用此属性修饰您不希望包含在sql
创建的Dapper
中的任何属性。所以在我的例子中,让我告诉Dapper排除我需要做的Category
字段:
public class Product {
public int Id {get;set;}
public string Name {get;set;}
[Write(false)] // tell Dapper to exclude this field from the sql
public ProductCategory Category {get;set;}
public int CategoryId {get;set;}
}
请记住我选择Contrib
的原因是因为change tracking
功能。 This SO thread,我在上面提到的链接,指出要启用更改跟踪,您需要为您的类设置一个接口,并将其与Contrib
一起使用。所以对于我的示例类,我需要:
public interface IProduct {
int Id {get;set;}
string Name {get;set;}
ProductCategory Category {get;set;}
int Category {get;set;}
}
// and implement it on my Product class
public class Product : IProduct {
public int Id {get;set;}
public string Name {get;set;}
[Write(false)]
public ProductCategory Category {get;set;}
int Category {get;set;}
}
我以为就是这样,差不多!您可能会问,如果Category
根本不关心它,我为什么需要在界面中定义Dapper
。事实上,这只会导致一个问题,这个问题我会解决。
在我的特定情况下,有时我需要处理Category
字段,同时维护Product
对象的更改跟踪。为了保持跟踪能力,get调用应该使用如下接口类型:
var product = connection.Get<IProduct>(id);
如果我不能在我的界面中定义它,那么我会不能够访问Category
字段。但是如果我在界面中定义它,那么我会得到熟悉的错误
{type}类型的成员{member}不能用作参数 值。
真的又来了吗?请停止。
无需担心,因为通过装饰接口成员就像我们为类所做的那样容易解决这个问题。因此,使一切工作的最终配置应该是:
public interface IProduct {
// I will not discuss here what this attribute does
// as this is documented already in the github source.
// Just take note that this is needed,
// both here and in the implementing class.
[Key]
int Id {get;set;}
string Name {get;set;}
[Write(false)]
ProductCategory Category {get;set;}
int Category {get;set;}
}
// and implement it on my Product class
public class Product : IProduct {
[Key]
public int Id {get;set;}
public string Name {get;set;}
[Write(false)]
public ProductCategory Category {get;set;}
int Category {get;set;}
}
如果您希望使用具有Contrib
功能的change tracking
,则可以使用此方法。如果您想使用Rainbow
并且导航属性出现问题,那么您可以play with my pull request。它的工作方式与WriteAttribute
的工作方式相同,只适用于Rainbow
。
如果您不喜欢使用属性来装饰您的类,那么这两个扩展项目都不适合您。我知道还有另一个扩展项目可以让你进行某种流畅的类型配置,但不与{{1}一起使用(不包括在核心部分中)在github中的库。我的偏好,只是与核心库一起工作,让我调查整个库,看看是否已经存在,或者是否可以改进以满足我的需求。这就是我在Dapper
和Rainbow
所做的和解释的内容。
我希望这个贡献,我添加的非常简单的类,我展示的配置提示,以及引导我这些的场景,将帮助将来希望使用Dapper的人,并且会有类似的设置,我有。此外,这个答案将教育开发者更多的Dapper可以做什么和不能做什么。这个很棒的工具名为Contrib
deserves a better wiki,我希望这篇答案/文章在SO中有所帮助。
**如果我在这里写的东西已经写在某个地方,我在两周时间内没有找到我一直在等待答案,那么我会很高兴有人把我链接到它。现在已经过了两个星期了,看过我问题的29个人没有提出任何链接或解决方案,所以我认为我在这里分享的信息对Dapper来说是新的* :)
注意:我修改了我的问题标题,以便其他人可以看到这个问题的潜在解决方案。新标题基于我获得的关于Dapper的新知识。
答案 1 :(得分:0)
不是答案,而是提醒...如果您来这里是为了寻找错误的解决方法:
类型{type}的成员{member}不能用作参数值。
不要忘记配置您的映射,例如:
DapperExtensions.DapperExtensions.SetMappingAssemblies(new List<Assembly>()
{
typeof(SomeClassMapConfig).Assembly,
// ...
});
然后...
public class SomeClassMapConfig : ClassMapper<SomeClass>
{
public SomeClassMapConfig()
{
Table("tablename");
Map(p => p.Id).Key(KeyType.Identity).Column("Id");
Map(p => p.SomeProp).Column("SomeField");
// etc..