NodaTime如何与EF Code First一起使用?

34

我真的很想在我的Entity Framework Code First数据库项目中使用NodaTime,但是还没有找到一个“干净”的方法来实现它。我真正想做的是这个:

public class Photoshoot
{
    public Guid PhotoshootId{get; set;}
    public LocalDate ShootDate{get; set;} //ef ignores this property
}

有没有使用NodaTime与EF Code First的支持或推荐方法?


6
万一有人期望我回答:我不知道。我不知道如何在EF中映射自定义数据类型。不过我会联系一个可能知道的人... - Jon Skeet
Jon:你目前在非EF数据库项目中如何存储NodaTime值? - smalltowndev
我已经使用Noda Time in RavenDB完成了这个任务,这只是因为RavenDB支持扩展其序列化和类型转换。我研究了EF,但我遇到了Colin描述的问题。 - Matt Johnson-Pint
我实际上并不开发.NET应用程序,所以简单的答案是“我不会” :) 但如果我这样做了,就有很多选项要考虑,基于上下文 - 我使用的Noda Time类型、数据库、其他客户端使用的数据库等。我记得我在某个地方写了一些用户指南,但现在找不到了... - Jon Skeet
4个回答

22

在 Entity Framework 中,如果没有本地支持自定义原始类型持久化的功能,一种常见的解决方法是使用 buddy 属性

针对您领域模型中的每个自定义原始类型,您需要创建一个关联的映射原始类型来保存 Entity Framework 支持的格式的值。然后,通过相应的 buddy 属性的值计算自定义原始类型的属性。

例如:

public class Photoshoot
{
    // mapped
    public Guid PhotoshootId{get; set;}

    // mapped buddy property to ShootDate
    public DateTime ShootDateValue { get; set; }

    // non-mapped domain properties
    public LocalDate ShootDate 
    {
        get { // calculate from buddy property }
        set { // set the buddy property }
    }
}

我们在代码中首先使用NodaTime POCO,完全采用这种方法。

显然,这使您只有一个类型可以作为代码优先POCO和域类型。通过将不同的职责分离出两个类型并在它们之间进行映射,可以在增加复杂性的同时改进此情况。一个折衷方案是将域属性推入子类型,并使所有映射好友属性受保护。通过一定程度的实体框架,可以将其映射到受保护的属性。

这篇非常出色的博客文章评估了Entity Framework对各种领域建模构造的支持,包括封装的原语。当我设置我们的POCO时,这就是我最初发现好友属性概念的地方: http://lostechies.com/jimmybogard/2014/04/29/domain-modeling-with-entity-framework-scorecard/

该系列中的另一篇博客文章讨论了映射到受保护属性:http://lostechies.com/jimmybogard/2014/05/09/missing-ef-feature-workarounds-encapsulated-collections/


这是我一直在使用的方法,但它感觉很“凌乱”。我为序列化LocalDate到/从整数(yyyymmdd)创建了一些小扩展方法,并将其放在名为ShootDateSerial的后备字段中。EF不能处理类型转换,这太糟糕了。 - smalltowndev
1
不幸的是,这是唯一可行的方法,原因如Colin所述。 - Matt Johnson-Pint
1
非常同意 - 这确实很混乱。然而,我使用 Entity Framework 的次数越多,就越发现一些缺失的功能,这些功能是我从其他 ORM 中期望得到的。也许我现在已经习惯了它,但我已经学会了接受它所能做的事情。 - Matt Caton
1
您也可以使用AutoMapper将POCO / DAL类(由EF原始类型支持)映射到具有Noda等类型的Domain类。 - abatishchev
1
有没有办法使用可空的DateTime来完成它?因为NodaTime的LocalDate是一个结构体。 - A_Arnold
我相信您可以使用一个私有的后备字段将其映射到数据库,并仅有一个公共属性。 - Code Name Jack

12

EF Core 2.1有一个新功能值转换(Value Conversions),专门解决这种情况。

//OnModelCreating
builder.Entity<MyEntity>
       .Property(e => e.SomeInstant)
       .HasConversion(v => v.ToDateTimeOffset(), v => Instant.FromDateTimeOffset(v));

.HasConversion 有其他重载版本可使此逻辑可重用,例如您可以定义自己的 ValueConverter


2
这种方法有一个严重的缺点:EFCore目前无法比较使用ValueConvertor映射到数据存储(至少是SQL)的自定义类型值,因此它只是将所有内容加载到内存中并进行过滤。请参见https://github.com/aspnet/EntityFrameworkCore/issues/11156。 - jahav
@jahav 是的,这是预期的,因为值转换器可能非常复杂,无法转换为SQL语句。请注意,使用伙伴属性也存在此问题。我们应该始终小心处理这个问题。 - Cheng Chen
@DannyChen,ValueConverter 的整个意义不就是获取一个可以转换为/从 SQL 语句中翻译的值吗?在我看来,基本比较应该非常容易实现。也许这更多是 EF Core 团队时间限制的原因? - Ortiga
想象一下,你有一个非常复杂的方法,可以将一个字符串转换为整数(还有另一个方法可以将其转换回来),它们被用在了.HasConversion方法中,但是这两个复杂的方法无法转换成SQL。 - Cheng Chen
1
@ChengChen 我认为你没有理解重点。你不需要将复杂方法的主体转换为SQL。你可以使用代码上的该方法对值进行转换,只有ValueConverter的输出将用于SQL。 - Ortiga
@ChengChen 假设你的复杂ValueConverter将字符串"foo"转换为整数1。Linq .Where(entity => entity.Bar == "foo") 可以转换为SQL WHERE [Table].[Bar] = 1。显然,会有一些限制和特殊情况,但对于像这样简单的情况,它可能是可行的。 - Ortiga

11

1
是的 - 没错。在 EF 允许扩展模型以支持自定义值类型之前,将无法实现对 Noda Time 的“干净”支持。 - Matt Johnson-Pint
哦,哇,回到 NHibernate,我的偏见观点是:如果没有自定义数据类型支持,所有的工具支持都不值得。 - Răzvan Flavius Panda

2

有一种针对Postgres(Npgsql)的特定提供程序方法可行。

安装该库。

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime 

在配置DbContext时,请使用以下内容:

services.AddDbContext<PhotoshootDbContext>(opt =>opt.UseNpgsql(Configuration.GetConnectionString("ConnectionString"), o => o.UseNodaTime()));

还有一些第三方库可以用于其他提供商。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接