Entity Framework Core 中的强类型 ID

Yve*_*lpe 11 c# domain-driven-design entity-framework entity-framework-core .net-core

我正在尝试创建一个强类型Id类,它现在在内部保存“long”。下面实现。我在实体中使用它的问题是实体框架给了我一条消息,表明属性Id已经映射到它上面。IEntityTypeConfiguration下面看我的。

注意:我的目标不是严格的 DDD 实现。所以请在评论或回答时记住这一点。类型化背后的整个 idId是供开发人员使用的,他们被强类型化为在所有实体中使用 Id 的项目,当然翻译为long(或BIGINT) - 但对于其他人来说很清楚。

在类和配置下面,这是行不通的。该存储库可以在https://github.com/KodeFoxx/Kf.CleanArchitectureTemplate.NetCore31找到,

Id类实现(现在标记为过时,因为在找到解决方案之前我放弃了这个想法)

namespace Kf.CANetCore31.DomainDrivenDesign
{
    [DebuggerDisplay("{DebuggerDisplayString,nq}")]
    [Obsolete]
    public sealed class Id : ValueObject
    {
        public static implicit operator Id(long value)
            => new Id(value);
        public static implicit operator long(Id value)
            => value.Value;
        public static implicit operator Id(ulong value)
            => new Id((long)value);
        public static implicit operator ulong(Id value)
            => (ulong)value.Value;
        public static implicit operator Id(int value)
            => new Id(value);


        public static Id Empty
            => new Id();

        public static Id Create(long value)
            => new Id(value);

        private Id(long id)
            => Value = id;
        private Id()
            : this(0)
        { }

        public long Value { get; }

        public override string DebuggerDisplayString
            => this.CreateDebugString(x => x.Value);

        public override string ToString()
            => DebuggerDisplayString;

        protected override IEnumerable<object> EquatableValues
            => new object[] { Value };
    }
}
Run Code Online (Sandbox Code Playgroud)

EntityTypeConfiguration我正在使用当 Id 没有标记为实体的过时Person不幸的是,当类型为 Id 时,EfCore 不想映射它......当类型为 long 时它没问题......其他拥有的类型,如您所见(带有Name)工作正常。

public sealed class PersonEntityTypeConfiguration
        : IEntityTypeConfiguration<Person>
    {
        public void Configure(EntityTypeBuilder<Person> builder)
        {
            // this would be wrapped in either a base class or an extenion method on
            // EntityTypeBuilder<TEntity> where TEntity : Entity
            // to not repeated the code over each EntityTypeConfiguration
            // but expanded here for clarity
            builder
                .HasKey(e => e.Id);
            builder
                .OwnsOne(
                e => e.Id,
                id => {
                   id.Property(e => e.Id)
                     .HasColumnName("firstName")
                     .UseIdentityColumn(1, 1)
                     .HasColumnType(SqlServerColumnTypes.Int64_BIGINT);
                }

            builder.OwnsOne(
                e => e.Name,
                name =>
                {
                    name.Property(p => p.FirstName)
                        .HasColumnName("firstName")
                        .HasMaxLength(150);
                    name.Property(p => p.LastName)
                        .HasColumnName("lastName")
                        .HasMaxLength(150);
                }
            );

            builder.Ignore(e => e.Number);
        }
    }
Run Code Online (Sandbox Code Playgroud)

Entity 基类(当我还在使用 Id 时,所以当它没有被标记为过时)

namespace Kf.CANetCore31.DomainDrivenDesign
{
    /// <summary>
    /// Defines an entity.
    /// </summary>
    [DebuggerDisplay("{DebuggerDisplayString,nq}")]
    public abstract class Entity
        : IDebuggerDisplayString,
          IEquatable<Entity>
    {
        public static bool operator ==(Entity a, Entity b)
        {
            if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
                return true;

            if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
                return false;

            return a.Equals(b);
        }

        public static bool operator !=(Entity a, Entity b)
            => !(a == b);

        protected Entity(Id id)
            => Id = id;

        public Id Id { get; }

        public override bool Equals(object @object)
        {
            if (@object == null) return false;
            if (@object is Entity entity) return Equals(entity);
            return false;
        }

        public bool Equals(Entity other)
        {
            if (other == null) return false;
            if (ReferenceEquals(this, other)) return true;
            if (GetType() != other.GetType()) return false;
            return Id == other.Id;
        }

        public override int GetHashCode()
            => $"{GetType()}{Id}".GetHashCode();

        public virtual string DebuggerDisplayString
            => this.CreateDebugString(x => x.Id);

        public override string ToString()
            => DebuggerDisplayString;
    }
}
Run Code Online (Sandbox Code Playgroud)

Person(域和对其他 ValueObject 的引用可以在https://github.com/KodeFoxx/Kf.CleanArchitectureTemplate.NetCore31/tree/master/Source/Core/Domain/Kf.CANetCore31.Core.Domain/People找到)

namespace Kf.CANetCore31.Core.Domain.People
{
    [DebuggerDisplay("{DebuggerDisplayString,nq}")]
    public sealed class Person : Entity
    {
        public static Person Empty
            => new Person();

        public static Person Create(Name name)
            => new Person(name);

        public static Person Create(Id id, Name name)
            => new Person(id, name);

        private Person(Id id, Name name)
            : base(id)
            => Name = name;
        private Person(Name name)
            : this(Id.Empty, name)
        { }
        private Person()
            : this(Name.Empty)
        { }

        public Number Number
            => Number.For(this);
        public Name Name { get; }

        public override string DebuggerDisplayString
            => this.CreateDebugString(x => x.Number.Value, x => x.Name);
    }
}
Run Code Online (Sandbox Code Playgroud)

Yve*_*lpe 4

So after searching a long while, and trying to get some more answer, I found it, here it is then. Thanks to Andrew Lock.

Strongly-typed IDs in EF Core: Using strongly-typed entity IDs to avoid primitive obsession - Part 4: https://andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-4/

TL;DR / Andrew 的摘要 在这篇文章中,我描述了一种通过使用值转换器和自定义 IValueConverterSelector 在 EF Core 实体中使用强类型 ID 的解决方案。EF Core 框架中的基础 ValueConverterSelector 用于注册基元类型之间的所有内置值转换。通过从此类派生,我们可以将强类型 ID 转换器添加到此列表,并在整个 EF Core 查询中获得无缝转换

  • 在 EF7 中,基于值转换器的键类型可以使用自动生成的键值,只要底层类型支持这一点。https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#improved-value- Generation (2认同)