我目前正在深入研究领域驱动设计 (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);
}
}
我在那里尝试了很多配置,但那是没有关系的。
我尝试调整你的代码,这是一个有效的解决方案:
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
不满意,并且正在创建一个从未设置的重复阴影属性,因此我只是删除了该(冗余)定义。希望有帮助!