如何使用Dapper.Rainbow(或可选地使用Dapper.Contrib)为具有导航属性的对象插入和更新

时间:2013-03-29 17:26:14

标签: orm dapper micro-orm dapper-rainbow

我最近刚开始研究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,这就是我探索的原因,并希望了解如何做到这一点。

2 个答案:

答案 0 :(得分:22)

不是Dapper.Rainbow

Dapper.Rainbow目前的形式无法做到这一点,但my pull request in github使这成为可能。

我很惊讶没有人建议使用Dapper.Contrib。我知道我问过功能是否在Rainbow中。但我没想到没有人会注意到这句话(特别是粗体文字):

  

现在我正在寻找能让你更容易做的事情   插入和更新,找到了Dapper.Rainbow。我检查了一下然后   能够使用它来获取和插入对象,如上所述。我的   问题是,当Product具有导航属性时,我无法做到   在该字段上插入

...并提出一个替代方案,即Dapper库中已有的解决方案。我想我应该更清楚我的问题并明确询问整个Dapper library that is in github中某处是否存在解决方案。因此,在对图书馆进行更多挖掘之后,我发现我的问题得到了支持。

Dapper.Contrib的路径

我的项目和Rainbow一切正常,直到我需要更多内容。我有一些表中有很多字段。如果我只是向Rainbow提供我的对象,那么它将对所有字段进行更新,这并不是一件好事。但这并不能让我迅速跳出船并返回NH。所以在我实施自己的change tracking之前,我并不想重新发明轮子,特别是如果有人已经做好了工作,我会用Google搜索found this SO thread。该帖子确认我知道 Rainbow不支持更改跟踪,但还有另一个野兽,它被称为Dapper.Contrib。所以我开始尝试它。

所以我们再见面

  

ProductCategory类型的成员类别不能用作   参数值

我遇到与Rainbow相同的问题。 Contrib不支持导航属性!?我开始觉得我在Dapper浪费我的时间,而且它提供的表现,我非常追求,只会是一厢情愿的想法。直到...

WriteAttribute,来救援......

此类位于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中的库。我的偏好,只是与核心库一起工作,让我调查整个库,看看是否已经存在,或者是否可以改进以满足我的需求。这就是我在DapperRainbow所做的和解释的内容。

我希望这个贡献,我添加的非常简单的类,我展示的配置提示,以及引导我这些的场景,将帮助将来希望使用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..