我在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);