EF Core - 使用值对象和父类型创建复合唯一索引

Mar*_*tin 6 c# unique-constraint value-objects entity-framework-core

我有一个带有ExternalSystemName 值对象和另一个实体的部署父类型的实体。该模型的重要部分如下所示:

public sealed class ExternalSystem : Entity
{
    public ExternalSystemName Name { get; private set; }

    public Deployment Deployment { get; private set; }
}
Run Code Online (Sandbox Code Playgroud)

该实体的唯一性由部署 ID(存储在部署实体类中)和名称(ExternalSystemName 值对象的值)的组合确定。换句话说,一个部署不能有 2 个同名的外部系统。

我在尝试使用 IEntityTypeConfiguration 实现设置此组合唯一索引时遇到问题:

internal sealed class ExternalSystemsConfiguration : 
IEntityTypeConfiguration<ExternalSystem>
{
    public void Configure(EntityTypeBuilder<ExternalSystem> builder)
    {
        builder.ToTable("TblExternalSystems");

        builder.OwnsOne(e => e.Name, navigationBuilder =>
        {
            navigationBuilder.Property(e => e.Value)
            .HasColumnName("Name");
        });

        builder.HasIndex(e => new { e.Name, e.Deployment }).IsUnique();
    }
}
Run Code Online (Sandbox Code Playgroud)

我在运行 API 时遇到此异常:

System.InvalidOperationException: ''Name' cannot be used as a property on entity type 'ExternalSystem' because it is configured as a navigation.'
Run Code Online (Sandbox Code Playgroud)

我尝试将索引指向 e.Name.Value,但收到此错误:

System.ArgumentException: 'The expression 'e => new <>f__AnonymousType0`2(Value = e.Name.Value, Deployment = e.Deployment)' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. When specifying multiple properties or fields, use an anonymous type: 't => new { t.MyProperty, t.MyField }'. (Parameter 'memberAccessExpression')'
Run Code Online (Sandbox Code Playgroud)

我还尝试了仅对这些属性之一使用唯一索引,但无论如何我都会收到导航错误。我担心我已经知道答案了,但这是否意味着 EF Core 仅支持非实体、非 valueObject 类型的列上的索引?这是否意味着我的模型需要有一个代表部署 ID 的 Guid 属性,而不是部署本身?

更新

我了解到 EF Core 可以很好地处理引用/原始对。考虑到这一点,我的ExternalSystem实体现在可以同时具有以下属性:

public Deployment Deployment { get; private set; }

public Guid DeploymentId { get; private set; }
Run Code Online (Sandbox Code Playgroud)

该 Guid 属性不是构造函数的一部分,因为它们最终获得相同的列名,所以一切正常。我现在可以将其添加到该实体的配置中,并且索引已正确创建:

builder.HasIndex(e => new { e.DeploymentId}).IsUnique();
Run Code Online (Sandbox Code Playgroud)

我现在的问题是值对象。使用相同的方法,我想我可以做这样的事情?

public ExternalSystemName NameV { get; private set; }

public string Name { get; private set; }
Run Code Online (Sandbox Code Playgroud)

我必须重命名值对象属性,因为它们显然不能共享相同的名称。这不是我必须对实体类型做的事情,因为 EF Core 知道首先将“Id”添加到列名称中。通过此设置,EF Core 将复制列。一个的名称为“Name”,另一个的名称为“ExternalSystem_Name”。显然,其他所有操作都会失败,因为该列不接受空值。为什么会发生这种情况?

Mar*_*tin 1

我通过为ExternalSystemName属性定义一个转换器来解决这个问题:

builder.Property(e => e.Name)
        .HasConversion(
            e => e.Value,
            v => ExternalSystemName.Create(v).Value);
Run Code Online (Sandbox Code Playgroud)

我现在可以这样声明我的唯一索引:

    builder.HasIndex(e => new { e.DeploymentId, e.Name}).IsUnique();
Run Code Online (Sandbox Code Playgroud)

自从提出这个问题以来,我还升级到了 .NET 7 和 EF Core 7,所以这可能也是一个因素。