使用 DDD 中的强类型 ID 对 EF Core 中的外键关系进行故障排除

问题描述 投票:0回答:1

我目前正在深入研究领域驱动设计 (DDD) 并通过 EF Core 将其付诸实践。在我的域模型中,我选择了强类型标识符。为了在 EF Core 中适应这一点,我利用域类型转换将这些标识符映射到 EF Core 支持的 Guid 类型。然而,我在尝试建立实体之间的关系时遇到了挑战。

在 EF Core 中配置关系时,我遇到了一个问题:EF Core 将指定用于保存外键的属性识别为不同类型,从而创建影子属性。

错误消息如下:

外键属性“Communities.UserId1”是在影子状态下创建的,因为实体类型中存在简单名称“UserId”的冲突属性,但要么未映射,要么已用于另一个关系,要么与关联的主键类型。

此外,在尝试解决此问题时,我没有转换属性,结果却遇到了另一个错误:

无法映射“UserId”属性“Communities.UserId”,因为数据库提供程序不支持此类型。

值得注意的是,如果我不建立任何关系,迁移过程就会顺利进行。然而,这会导致缺乏关系,从而导致属性内的不一致。

您能否提供指导来解决这些问题并配置 EF Core 以正确建立关系,同时在 DDD 上下文中容纳强类型标识符?

下面是我的域上下文的示例

这是我的社区域名

 public sealed class Community
    : AggregateRoot<CommunityId, Guid>
{
    public UserId UserId { get; private set; }
    public string Name { get; private set; }
    public string Description { get; private set; }
    public string Topic { get; private set; }
    public DateTime CreatedAt { get; private set; }
    public DateTime UpdatedAt { get; private set; }
}

有聚合根

    public abstract class AggregateRoot<TId, TIdType> : Entity<TId> where TId : AggregateRootId<TIdType>
{
    public new AggregateRootId<TIdType> Id { get; protected set; }

    protected AggregateRoot(TId id)
    {
        Id = id;
    }

    protected AggregateRoot(){}

}

有AggregaterootId

public abstract class AggregateRootId<TId> : ValueObject
{
    public abstract TId Value { get; protected set; }
}

有实体

    public abstract class Entity<TId> : IEquatable<Entity<TId>>
where TId : notnull
{
    public TId Id { get; protected set; }

    public Entity() { }

    protected Entity(TId id)
    {
        Id = id;
    }

    public override bool Equals(object? obj)
    {
        return obj is Entity<TId> entity && Id.Equals(entity.Id);
    }

    public bool Equals(Entity<TId>? other)
    {
        return Equals((object?)other);
    }

    public static bool operator ==(Entity<TId> left, Entity<TId> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(Entity<TId> left, Entity<TId> right)
    {
        return !Equals(left, right);
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

有值对象

    public abstract class ValueObject: IEquatable<ValueObject>
{
    public abstract IEnumerable<object> GetEqualityComponents();
    public override bool Equals(object? obj)
    {
        if(obj == null || obj.GetType() != GetType())
        {
            return false;
        }

        var valueObject = (ValueObject)obj;

        return GetEqualityComponents()
            .SequenceEqual(valueObject.GetEqualityComponents());
    }

    public static bool operator ==(ValueObject left, ValueObject right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObject left, ValueObject right)
    {
        return !Equals(left, right);
    }

    public override int GetHashCode()
    {
       return GetEqualityComponents()
        .Select(x => x?.GetHashCode() ?? 0)
        .Aggregate((x, y) => x ^ y);
    }

    public bool Equals(ValueObject? other)
    {
        return Equals((object?)other);
    }
}

有userId类型

    public sealed class UserId : AggregateRootId<Guid>
{
    public override Guid Value { get; protected set; }

    public UserId(Guid value)
    {
        Value = value;
    }

    public static UserId CreateUnique()
    {
        return new(Guid.NewGuid());
    }

    public static UserId Create(Guid guid)
    {
        return new UserId(guid);
    }

    public override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}

最后但并非最不重要的

我的 EfCore 社区配置

 public class CommunityConfiguration
 : IEntityTypeConfiguration<Community>
{
    public void Configure(EntityTypeBuilder<Community> builder)
    {
        ConfigureCommunityTable(builder);
    }

    private void ConfigureCommunityTable(EntityTypeBuilder<Community> builder)
    {
        builder.ToTable("Communities");

        builder.HasKey(c => c.Id);

        builder.Property(c => c.Id)
             .ValueGeneratedNever()
             .HasColumnName("Id")
             .HasConversion(id => id.Value,
                 value => CommunityId.Create(value));

        builder.Property(c => c.UserId)
             .ValueGeneratedNever()
             .HasColumnName("UserId")
             .HasConversion(id => id.Value,
                 value => UserId.Create(value));

        builder.Property(c => c.Name)
            .HasMaxLength(100);

        builder.Property(c => c.Description)
            .HasMaxLength(200);

        builder.Property(c => c.Topic)
            .HasMaxLength(100);

        builder.Property(c => c.CreatedAt);

        builder.Property(c => c.UpdatedAt);
    }
}

我在那里尝试了很多配置,但那是没有关系的。

entity-framework-core foreign-keys domain-driven-design relationship strong-typing
1个回答
0
投票

我尝试调整你的代码,这是一个有效的解决方案:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

var serviceCollection = new ServiceCollection();

serviceCollection.AddDbContext<MyDb>(o =>
{
    o.UseInMemoryDatabase("DDD");
});

var services = serviceCollection.BuildServiceProvider();

var db = services.GetRequiredService<MyDb>();

var stackOverflow = new Community("StackOverflow");
var marco = new User("Marco");

stackOverflow.Users.Add(marco);

marco.Community = stackOverflow;

db.Communities.Add(stackOverflow);

db.SaveChanges();

foreach (var community in db.Communities)
{
    Console.WriteLine($"Members of {community.Name}:");
    foreach (var user in community.Users)
    {
        Console.WriteLine(user.Name);
    }
}

class MyDb : DbContext
{
    public MyDb()
    {
    }

    public MyDb(DbContextOptions options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Community>(builder =>
        {
            builder.ToTable("Communities");

            builder.HasKey(c => c.Id);

            builder.Property(c => c.Id)
                 .ValueGeneratedNever()
                 .HasConversion(id => id.Value,
                     value => CommunityId.Create(value));

            builder.Property(c => c.Name)
                .HasMaxLength(100);

            builder.HasMany(c => c.Users)
                .WithOne(u => u.Community)
                .HasForeignKey("CommunityId");

        });

        modelBuilder.Entity<User>(builder =>
        {
            builder.ToTable("Users");

            builder.HasKey(u => u.Id);

            builder.Property(u => u.Name)
                .HasMaxLength(100);

            builder.Property(u => u.Id)
                 .ValueGeneratedNever()
                 .HasConversion(id => id.Value,
                     value => UserId.Create(value));

            builder.Property<CommunityId>("CommunityId")
                .HasConversion(x => x.Value, g => CommunityId.Create(g));
        });
    }
    public virtual DbSet<Community> Communities { get; set; }
}

public class Community
   : AggregateRoot<CommunityId, Guid>
{
    public Community(string name)
    {
        Id = CommunityId.CreateUnique();
        Name = name;
    }
    public string Name { get; private set; }
    public virtual ICollection<User> Users { get; set; } = new HashSet<User>();
}

public class User : Entity<UserId>
{
    public User(string name)
    {
        Id = UserId.CreateUnique();
        Name = name;
    }
    public string Name { get; set; }
    public virtual Community Community { get; set; } = null!;
}
public abstract class AggregateRoot<TId, TIdType> : Entity<TId> where TId : AggregateRootId<TIdType>
{
    protected AggregateRoot(TId id)
    {
        Id = id;
    }

    protected AggregateRoot() { }

}

public abstract class AggregateRootId<TId> : ValueObject
{
    public abstract TId Value { get; protected set; }
}

public abstract class Entity<TId> : IEquatable<Entity<TId>>
where TId : notnull
{
    public TId Id { get; protected set; }

    public Entity() { }

    protected Entity(TId id)
    {
        Id = id;
    }

    public override bool Equals(object? obj)
    {
        return obj is Entity<TId> entity && Id.Equals(entity.Id);
    }

    public bool Equals(Entity<TId>? other)
    {
        return Equals((object?)other);
    }

    public static bool operator ==(Entity<TId> left, Entity<TId> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(Entity<TId> left, Entity<TId> right)
    {
        return !Equals(left, right);
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

public abstract class ValueObject : IEquatable<ValueObject>
{
    public abstract IEnumerable<object> GetEqualityComponents();
    public override bool Equals(object? obj)
    {
        if (obj == null || obj.GetType() != GetType())
        {
            return false;
        }

        var valueObject = (ValueObject)obj;

        return GetEqualityComponents()
            .SequenceEqual(valueObject.GetEqualityComponents());
    }

    public static bool operator ==(ValueObject left, ValueObject right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObject left, ValueObject right)
    {
        return !Equals(left, right);
    }

    public override int GetHashCode()
    {
        return GetEqualityComponents()
         .Select(x => x?.GetHashCode() ?? 0)
         .Aggregate((x, y) => x ^ y);
    }

    public bool Equals(ValueObject? other)
    {
        return Equals((object?)other);
    }
}
public sealed class UserId : AggregateRootId<Guid>
{
    public override Guid Value { get; protected set; }

    public UserId(Guid value)
    {
        Value = value;
    }

    public static UserId CreateUnique()
    {
        return new(Guid.NewGuid());
    }

    public static UserId Create(Guid guid)
    {
        return new UserId(guid);
    }

    public override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}
public sealed class CommunityId : AggregateRootId<Guid>
{
    public override Guid Value { get; protected set; }

    public CommunityId(Guid value)
    {
        Value = value;
    }

    public static CommunityId CreateUnique()
    {
        return new(Guid.NewGuid());
    }

    public static CommunityId Create(Guid guid)
    {
        return new CommunityId(guid);
    }

    public override IEnumerable<object> GetEqualityComponents()
    {
        yield return Value;
    }
}

请注意,我为 FK 使用了影子属性。

我发现的主要问题是 EF Core 对

public new TId Id
中的
AggregateRoot
不满意,并且正在创建一个从未设置的重复阴影属性,因此我只是删除了该(冗余)定义。希望有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.