实体框架核心中的强类型ID


12

我正在尝试创建一个强类型的Id类,该类现在在内部保持“ long”。实施如下。我在实体中使用此控件时遇到的问题是,实体框架向我发送一条消息,指出属性ID已映射到其上。请参阅IEntityTypeConfiguration下面的内容。

注意:我的目标不是严格的DDD实现。因此,在评论或回答时记住这一点。键入后的整个id Id是供开发人员进入的项目,他们被强烈键入要在其所有实体中使用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 };
    }
}

EntityTypeConfiguration我在使用未将实体标记为过时的IdPerson不幸的是,尽管如此,当类型为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中找到域和对其他ValueObject的引用)

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);
    }
}

Answers:


3

我的目标不是严格执行DDD。因此,在评论或回答时请记住这一点。键入的ID后面的整个ID适用于进入该项目的开发人员,他们被强烈键入要在其所有实体中使用ID

然后为什么不添加类型别名:

using Id = System.Int64;

当然,我喜欢这个主意。但是,每次您在.cs文件中使用“ Id”时,是否不必确保将using语句放在顶部?在传递类的同时,不必这样做吗?另外,我将失去其他基本类功能,例如Id.Empty...,或者必须以扩展方法来实现它,然后...我喜欢这个主意,想一想。如果没有其他解决方案,我会为此解决,因为这清楚地表明了意图。
伊夫·斯切尔普

3

因此,搜索了很长一段时间,并尝试获得更多答案后,我找到了,就在这里。感谢安德鲁·洛克(Andrew Lock)。

EF Core中的强类型ID:使用强类型实体ID避免原始痴迷-第4部分https//andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity- ids避免原始强迫症第4部分/

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


2

我觉得你不走运。您的用例极为罕见。EF Core 3.1.1仍在努力将SQL放到数据库中,除了最基本的情况外,SQL在任何情况下都不会中断。

因此,您将不得不编写遍历LINQ树的内容,这可能是大量的工作,而且如果您偶然发现EF Core上的错误,您将很乐意在票证中进行解释。


我同意用例很少,但是我希望它背后的想法并不完全愚蠢……?如果是这样,请告诉我。如果它是愚蠢的(到目前为止还不能说服,因为强类型的ID在域中很容易编程),或者如果我没有快速找到答案,则可以使用David Browne建议的别名-下面的Micrososft(stackoverflow .com / a / 60155275/1155847)。到目前为止,在其他用例以及EF Core中的集合和隐藏字段方面都表现出色,没有错误,因此我认为这很奇怪,否则我对该产品具有扎实的良好经验。
伊夫·斯切尔普

它本身并不愚蠢,但是很少见到我从未见过支持它的消息,而EfCore是如此糟糕,以至于我现在正在努力将其删除并移回Ef(非核心),因为我需要发货。对我来说,EfCore 2.2效果更好-3.1是100%不可用的,因为我使用的任何投影都会导致SQL错误或“我们不再评估客户端”,即使-2.2确实在服务器上进行了评估。因此,我不希望他们在诸如此类的事情上花费时间-尽管他们的核心功能已被破坏。github.com/dotnet/efcore/issues/19830#issuecomment-584234667了解更多详细信息
TomTom

EfCore 3.1损坏了,这是EfCore团队决定不再评估客户端的原因,他们甚至在2.2版中发布有关此方面的警告,以使您为即将进行的更改做好准备。对此,我看不到那件特别的事坏了。至于其他我无法评论的内容,我已经看到了问题,但是能够解决这些问题而无需支付任何性能成本。另一方面,在我生产的最后3个项目中,有2个基于Dapper,一个基于Ef。。。也许我的目标是走这条Dapper的路线,但打败了新开发者轻松入职的目的:-)... 走着瞧。
伊夫·斯切尔普

问题是什么是服务器端评估的定义。他们甚至吹嘘无瑕的非常简单的东西。翻录功能,直到用完为止。我们只是删除EfCore,然后回到EF。EF +进行全局过滤的第三方=工作。dapper的问题是,我允许每个复杂的用户决定LINQ-我必须将其从bo转换为服务器端查询。在Ef 2.2中工作,现在完全感到厌烦。
TomTom

好的,我现在阅读此github.com/dotnet/efcore/issues/19679#issuecomment-583650245 ...我明白您的意思了,那么您使用的是哪个第三方lib?您能改一下您对Dapper的看法吗,因为我不明白您的意思。对我来说,它奏效了,但团队中只有2个开发人员的项目是低调的-当然要写很多手工样板以使其有效地工作……
Yves Schelpe
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.