简体   繁体   English

Entity Framework Core 中的强类型 ID

[英]Strongly Typed Ids in Entity Framework Core

I'm trying to have a strongly typed Id class, which now holds 'long' internally.我正在尝试拥有一个强类型的Id类,它现在在内部保持“long”。 Implementation below.下面实现。 The problem I'm having the using this in my entities is that Entity Framework gives me a message that the property Id is already mapped onto it.我在实体中使用它的问题是实体框架给了我一条消息,表明属性Id已经映射到它上面。 See my IEntityTypeConfiguration below.请参阅下面的IEntityTypeConfiguration

Note: I am not aiming to have a rigid DDD implementation.注意:我的目标不是严格的 DDD 实现。 So please keep this in mind when commenting or answering .所以请在评论或回答时记住这一点 The whole id behind the typed Id is for developers coming to the project they're strongly typed to use Id in all of their entities, of course translated to long (or BIGINT ) - but it is clear then for others.类型化Id背后的整个 id 是为进入项目的开发人员提供的,他们被强类型化为在所有实体中使用 Id,当然转换为long (或BIGINT ) - 但对其他人来说很清楚。

Below the class & configuration, which doesn't work.在类和配置下面,这是行不通的。 The repo can be found at https://github.com/KodeFoxx/Kf.CleanArchitectureTemplate.NetCore31 ,该存储库可以在https://github.com/KodeFoxx/Kf.CleanArchitectureTemplate.NetCore31找到,

Id class implementation (marked obsolete now, because I abandoned the idea until I found a solution for this) 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 };
    }
}

EntityTypeConfiguration I was using when Id not marked obsolete for entity Person Unfortunately though, when of type Id, EfCore didn't want to map it... when of type long it was no problem... Other owned types, as you see (with Name ) work fine. 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);
        }
    }

Entity base class (when I was still using Id, so when it wasn't marked obsolete) 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;
    }
}

Person (the domain and references to the other ValueObjects can be found at https://github.com/KodeFoxx/Kf.CleanArchitectureTemplate.NetCore31/tree/master/Source/Core/Domain/Kf.CANetCore31.Core.Domain/People ) 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);
    }
}

I am not aiming to have a rigid DDD implementation.我的目标不是严格的 DDD 实现。 So please keep this in mind when commenting or answering.所以请在评论或回答时记住这一点。 The whole id behind the typed Id is for developers coming to the project they're strongly typed to use Id in all of their entities类型化 Id 背后的整个 id 供开发人员使用,他们是强类型化的项目,以便在所有实体中使用 Id

Then why not just add a type alias:那么为什么不添加一个类型别名:

using Id = System.Int64;

I think you are out of luck.我觉得你运气不好。 Your use case is extremely rare.您的用例极为罕见。 And EF Core 3.1.1 is still struggling with putting SQL onto the database that is not broken in anything except the most base cases. EF Core 3.1.1 仍在努力将 SQL 放入数据库中,除了最基础的情况外,该数据库在任何情况下都没有损坏。

So, you would have to write something that goes through the LINQ tree and this likely is a tremendous amount of work, and if you stumble onto bugs on EF Core - which you will - have fun explaining that in your tickets.因此,您必须编写一些贯穿 LINQ 树的内容,这可能是一项巨大的工作,如果您偶然发现 EF Core 上的错误(您会发现),请在您的票证中对此进行有趣的解释。

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/ EF Core 中的强类型 ID:使用强类型实体 ID 避免原始痴迷 - 第 4 部分https : //andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity- ids-to-avoid-primitive-obsession-part-4/

TL;DR / Summary of Andrew In this post I describe a solution to using strongly-typed IDs in your EF Core entities by using value converters and a custom IValueConverterSelector. TL;DR / Andrew 总结在这篇博文中,我描述了通过使用值转换器和自定义 IValueConverterSelector 在 EF Core 实体中使用强类型 ID 的解决方案。 The base ValueConverterSelector in the EF Core framework is used to register all built-in value conversions between primitive types. EF Core 框架中的基础 ValueConverterSelector 用于注册基元类型之间的所有内置值转换。 By deriving from this class, we can add our strongly-typed ID converters to this list, and get seamless conversion throughout our EF Core queries通过从此类派生,我们可以将强类型 ID 转换器添加到此列表中,并在整个 EF Core 查询中实现无缝转换

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM