(嵌套的)拥有类型的EF Core域范围的值转换



我在EF Core中设置了几个值对象(DDD范式)作为拥有的类型。

EF Core支持配置私有类型,通过Owned()方法,它将自动将所有对给定类型的引用视为私有类型。

然而,我似乎找不到一种方法来指定它们的配置,特别是值转换,以类似的,集中的方式。

    {   // Configure value objects as owned types.
        builder.Owned(typeof(Money));
        builder.Owned(typeof(Currency));
        builder.Owned(typeof(Address));
        builder.Owned(typeof(Mass));
        builder.Owned(typeof(MassUnit));
        // Store and restore mass unit as symbol.
        builder.Entity<Product>()
            .OwnsOne(p => p.Mass, c => c.Property(m => m.Unit)
                .HasConversion(
                    u => u.Symbol,
                    s => MassUnit.FromSymbol(s))
                .HasMaxLength(3)
            );
    }

正如你在上面看到的,有一个为MassUnit配置的值转换,它是一个嵌套在Mass中的值对象。

但是我必须为使用值对象的所有地方手动执行此操作。例如,我已经在3个不同的地方使用了Money类型,并且该类型包含Currency,我希望为其配置类似的值转换。

是否有任何(好的)方法来为所拥有的类型指定通用的、域范围的配置?

我已经尝试过通过builder.Entity<Mass>().Property(m => m.Unit).HasConversion(..)配置它们,但是如果你试图通过Entity<>配置一个拥有的类型,EF Core似乎会抛出。

这可以使用共享ValueConverter:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var converter = new ValueConverter<EquineBeast, string>(
        v => v.ToString(),
        v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion(converter);
}

Update:实际上我在EF Core 6中发现了一个可以用于中央配置的新功能。🥳这叫做预约定模型配置。在你的DB Context类中,你可以定义一个新的ConfigureConventions()方法覆盖来接受一个ModelConfigurationBuilder实例。

这样,我在"过时的答案"一节中提供的旧示例可以变成:

protected override void ConfigureConventions(ModelConfigurationBuilder configBuilder)
{
    configBuilder.Properties<Currency>()
        .HaveConversion<CurrencyConverter>()
        .HaveMaxLength(3);
}
// Create the converter:
public class CurrencyConverter : ValueConverter<Currency, string>
{
    public CurrencyConverter()
        : base(
            currency => currency.Code,
            currencyCode => Currency.FromCode(currencyCode))
    {}
}

我唯一不喜欢的是,我们被迫定义转换器类,而不是能够使用lambda。对于像这样简单的转换,它绝对感觉像太多的仪式。

另外,不要忘记,没有显式转换的拥有类型仍然必须配置为.Owned<>(),因此在我的示例中,我应该在OnModelCreating()中配置builder.Owned<Money>()。我认为在OnModelCreating中保持.Owned<>()配置有点令人困惑,因为这也是给定类型的集中/通用配置。

奖金提示:您还可以集中配置字符串长度和十进制精度:

// So, this awkward old solution:
protected override void OnModelCreating(ModelBuilder builder)
{
    foreach (var entityType in builder.Model.GetEntityTypes())
    {
        foreach (var decimalProperty in entityType.GetProperties()
            .Where(x => x.ClrType == typeof(decimal)))
        {
            decimalProperty.SetPrecision(18);
            decimalProperty.SetScale(4);
        }
    }
}
// Can become:
protected override void ConfigureConventions(ModelConfigurationBuilder configBuilder)
{
    configBuilder.Properties<decimal>()
        .HavePrecision(precision: 18, scale: 4);
}

过时的答案:我仍然没有找到一个实际的解决方案,甚至EF Core 6似乎没有就此进行功能改进。但是,为了回答博德根在评论中的问题,我采取的变通办法如下:本质上与Ionix建议的相似,只是更进一步:

// Store and restore currency as currency code.
builder.Entity<Product>().OwnsOne(p => p.Price, StoreCurrencyAsCode);
builder.Entity<Transaction>().OwnsOne(p => p.Total, StoreCurrencyAsCode);
builder.Entity<TransactionLine>().OwnsOne(p => p.UnitPrice, StoreCurrencyAsCode);
static void StoreCurrencyAsCode<T>(OwnedNavigationBuilder<T, Money> onb) where T : class
    => onb.Property(m => m.Currency)
        .HasConversion(
            c => c.Code,
            c => Currency.FromCode(c))
        .HasMaxLength(3);

最新更新