14

我最近才开始研究 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.RainbowCategory在插入或更新时忽略。

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,这就是为什么我正在探索并想了解如何完成这样的事情。

4

2 回答 2

26

不是 Dapper.Rainbow

Dapper.Rainbow这在当前形式下是不可能的,但我在 github 中的拉取请求使这成为可能。

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

现在我正在寻找可以更轻松地进行插入和更新的东西,并找到了 Dapper.Rainbow。我检查了它,并能够使用它来获取和插入如上所述的对象。我的 问题是,当 Product 具有导航属性时,我无法在该字段上进行插入

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

Dapper.Contrib 的路径

我的项目一切正常,Rainbow直到我需要更多。我得到了一些包含很多字段的表。如果我只是将我的对象提供给 Rainbow,那么它将对所有字段进行更新,这并不是很好。但这并没有让我迅速跳下船回到新罕布什尔州。所以在我实现自己的之前change tracking,我不想重新发明轮子,特别是如果有人已经做得很好,我搜索了一下,发现了这个 SO 线程。该线程证实了我Rainbow不支持更改跟踪的知识,但还有另一只野兽支持,它被称为Dapper.Contrib. 所以我开始尝试它。

于是我们又见面了

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

我遇到了与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功能。这个 SO 线程,与我在上面给出的相同链接,指出要启动更改跟踪,您需要为您的类提供一个接口并将其与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并且像我一样遇到导航属性问题,那么您可以使用我的 pull request。它的工作方式与它WriteAttribute唯一的工作方式相同Rainbow

如果你不喜欢用属性装饰你的类,那么这两个扩展项目都不适合你。我知道还有另一个扩展项目可以让您进行某种流利类型的配置,但这不Dappergithub 中的库一起使用(不作为核心部分包含) 。我的偏好是仅使用核心库,这引导我调查整个库,看看是否已经存在或者是否可以对其进行改进以满足我的需求。这就是我在这里所做和解释的,对于RainbowContrib

我希望这个贡献,我添加的非常简单的类,我展示的配置技巧,以及引导我这些的场景,将帮助未来希望使用 Dapper 的人,并且会有与我类似的设置。此外,这个答案将使开发人员更多地了解 Dapper 能做什么和不能做什么。这个很棒的工具Dapper 值得一个更好的 wiki,我希望这个答案/文章在 SO 中即使在很小的方面也能有所帮助。


**如果我在这里写的东西已经写在某个地方,我在等待答案的两周时间内没有发现,那么我很高兴有人能将我链接到它。现在已经两周了,调查我的问题的 29 人没有提出任何链接或解决方案,所以我认为我在这里分享的信息对 Dapper* 来说是新的:)

注意:我修改了我的问题标题,以便其他人可以看到他们问题的潜在解决方案。新标题是基于我获得的关于 Dapper 的新知识。

于 2013-04-12T06:31:23.270 回答
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..
于 2019-08-28T01:01:56.063 回答