【问题标题】:Strongly Typed Ids in Entity Framework CoreEntity Framework Core 中的强类型 ID
【发布时间】:2020-02-10 16:48:56
【问题描述】:

我正在尝试创建一个强类型的Id 类,它现在在内部拥有“long”。下面实现。 我在实体中使用它的问题是 Entity Framework 给了我一条消息,即属性 Id 已经映射到它上面。请参阅下面的IEntityTypeConfiguration

注意:我的目标不是严格执行 DDD。所以请在评论或回答时记住这一点。输入 Id 背后的整个 id 是为进入项目的开发人员准备的,他们强烈输入在所有实体中使用 Id,当然翻译为 long(或 BIGINT) - 但很明显其他人。

在类和配置之下,这是行不通的。 repo 可以在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 };
    }
}

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 基类(当时我还在使用 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(可以在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);
    }
}

【问题讨论】:

    标签: c# entity-framework .net-core entity-framework-core domain-driven-design


    【解决方案1】:

    所以在搜索了很长时间并试图获得更多答案之后,我找到了它,那就是它。感谢安德鲁·洛克。

    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 / Andrew 的总结 在这篇文章中,我描述了一种通过使用值转换器和自定义 IValueConverterSelector 在 EF Core 实体中使用强类型 ID 的解决方案。 EF Core 框架中的基本 ValueConverterSelector 用于注册基元类型之间的所有内置值转换。通过从此类派生,我们可以将强类型 ID 转换器添加到此列表中,并在整个 EF Core 查询中实现无缝转换

    【讨论】:

      【解决方案2】:

      我认为你运气不好。您的用例极为罕见。 EF Core 3.1.1 仍在努力将 SQL 放到数据库中,除了大多数基本情况外,该数据库在任何情况下都没有损坏。

      因此,您将不得不编写一些通过 LINQ 树的内容,这可能是一项巨大的工作,如果您偶然发现 EF Core 上的错误 - 您会很高兴在您的票证中解释这一点。

      【讨论】:

      • 我同意这个用例很少见,但我希望它背后的想法并不完全愚蠢......?如果是这样,请告诉我。如果它很愚蠢(到目前为止还没有被说服,因为强类型的 id 在域中很容易编程),或者如果我没有快速找到答案,我可能会使用 David Browne - Micrososft 下面建议的别名(@ 987654321@)。到目前为止,其他用例以及 EF Core 中的集合和隐藏字段都很好,没有错误,所以我觉得这很奇怪,否则我对产品有很好的体验。
      • 这本身并不愚蠢,但很少有我见过的没有 orm 支持它,而且 EfCore 太糟糕了,以至于现在我正在努力删除它并移回 Ef(非核心)因为我需要发货。对我来说,EfCore 2.2 效果更好 - 3.1 100% 无法使用,因为我使用的任何投影都会导致错误的 sql 或“我们不再评估客户端”,即使 - 2.2 完美地在服务器上进行了评估。所以,我不希望他们花时间在这样的事情上——而他们的核心功能被破坏了。 github.com/dotnet/efcore/issues/19830#issuecomment-584234667了解更多详情
      • EfCore 3.1 坏了,EfCore 团队决定不再评估客户端是有原因的,他们甚至在 2.2 中发出警告,让您为即将发生的变化做好准备。至于那个,我不认为那个特别的东西坏了。至于其他我无法评论的东西,我已经看到了问题,但能够在没有任何性能成本的情况下解决它们。另一方面,在我为生产所做的最后 3 个项目中,其中 2 个是基于 Dapper 的,一个基于 Ef ......也许我应该针对这个项目走 dapper 路线,但这违背了新开发人员轻松进入的目的:-)...我们拭目以待。
      • 问题在于服务器端评估的定义。他们甚至吹嘘完美无瑕的非常简单的东西。删除功能,直到它无用为止。我们只需删除 EfCore 并返回到 EF。 EF + 3rd 用于全局 lfiltering = 工作。 dapper 的问题是我允许每个复杂的用户决定 LINQ - 我必须将它从 bo 转换为服务器端查询。在 Ef 2.2 中工作,现在完全无聊。
      • 好的,我现在读到这个github.com/dotnet/efcore/issues/19679#issuecomment-583650245... 我明白你的意思那你使用什么第三方库?你能否改写你所说的关于 Dapper 的内容,因为我不明白你的意思。对我来说它是有效的,但这是一个低调的项目,团队中只有 2 名开发人员 - 当然还有很多手动样板要编写以使其高效工作......
      【解决方案3】:

      我的目标不是严格执行 DDD。因此,请在评论或回答时记住这一点。类型化 Id 背后的整个 id 是为进入项目的开发人员准备的,他们被强类型化以在其所有实体中使用 Id

      那为什么不直接添加类型别名:

      using Id = System.Int64;
      

      【讨论】:

      • 当然,我喜欢这个主意。但是每次您将在 .cs 文件中使用“Id”时,您是否必须确保将这个 using 语句放在最上面 - 虽然传递了一个类,但不必这样做?此外,我会失去其他基类功能,例如Id.Empty...,或者必须在扩展方法中实现它......我喜欢这个想法,谢谢你的思考。如果没有其他解决方案出现,我会接受这个,因为这清楚地表明了意图。
      • 这不会阻止您将一个 id 用于不同的实体。强类型 id PersonId 将无法查询具有相同数字 id 的发票
      • 这就是你担心的事情?
      猜你喜欢
      • 2018-12-01
      • 2017-10-19
      • 2019-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-21
      • 2017-05-16
      • 2019-05-10
      相关资源
      最近更新 更多