Сильно набрані ідентифікатори в Entity Framework Core


12

Я намагаюсь мати сильно набраний Idклас, який зараз тримається 'довго' всередині. Впровадження нижче. Проблема, якою я користуюсь цим у своїх об'єктах, полягає в тому, що Entity Framework дає мені повідомлення про те, що Id властивості вже відображено на ньому. Дивіться мою IEntityTypeConfigurationнижче.

Примітка. Я не прагну мати жорсткої реалізації DDD. Тому, будь ласка, пам’ятайте про це, коментуючи чи відповідаючи . Весь ідентифікатор, який стоїть за типом, 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Я використовував, коли Id не позначено застарілим для сутностіPerson На жаль, хоча, коли типу Id, EfCore не хотів його відображати ... коли тип довго не було проблемою ... Інші типи, що належать, як ви бачите (з 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(домен та посилання на інші ValueObjects можна знайти на веб- сайті 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);
    }
}

Відповіді:


3

Я не маю на меті мати жорстку реалізацію DDD. Тому, будь ласка, пам’ятайте про це, коментуючи чи відповідаючи. Весь ідентифікатор, що стоїть за набраним Id, призначений для розробників, які підходять до проекту, на якому вони сильно набрані, щоб використовувати Id у всіх своїх об'єктах

Тоді чому б просто не додати псевдонім типу:

using Id = System.Int64;

Звичайно, ідея мені подобається. Але кожен раз, коли ви будете використовувати "Id" у файлі .cs, чи не доведеться вам обов'язково розміщувати це за допомогою оператора там зверху - в той час, коли клас передається навколо, цього не потрібно? Також я втратив би інші функціональні можливості базового класу, такі як Id.Empty..., або мені доведеться реалізувати його інакше в методі розширення, то ... Мені подобається ідея, thx для роздумів. Якщо не знайдеться інше рішення, я б погодився з цим, оскільки це чітко говорить про наміри.
Ів Шелпе

3

Отож, довго шукаючи, і намагаючись отримати ще якусь відповідь, я знайшов це, ось воно тоді. Завдяки Ендрю Локу.

Сильно введені ідентифікатори в EF Core: Використання сильно типізованих ідентифікаторів об'єктів для уникнення примітивної одержимості - Частина 4 : https://andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity- ІД-уникнути-примітив-одержимість-частина-4 /

TL; DR / Підсумок Ендрю У цій публікації я описую рішення щодо використання сильно типізованих ідентифікаторів у ваших об'єктах EF Core за допомогою перетворювачів значень та користувальницького IValueConverterSelector. Базовий ValueConverterSelector в рамці EF Core використовується для реєстрації всіх вбудованих перетворень значень між примітивними типами. Виходячи з цього класу, ми можемо додати до цього списку наші сильно типізовані перетворювачі ідентифікаторів та отримати безперебійну конверсію в межах наших запитів EF Core


2

Я думаю, що вам не пощастило. Ваш випадок використання надзвичайно рідкісний. І EF Core 3.1.1 все ще бореться з розміщенням SQL в базі даних, яка не порушена ні в чому, крім більшості базових випадків.

Отже, вам доведеться написати щось, що проходить через дерево LINQ, і це, швидше за все, величезна робота, і якщо ви натрапите на помилки на EF Core - що вам доведеться - весело пояснюючи це у своїх квитках.


Я погоджуюсь, що випадок використання зустрічається рідко, але ідея, що стоїть за ним, не зовсім дурна, я можу сподіватися ...? Якщо так, будь ласка, повідомте мене про це. Якщо це нерозумно (переконаний , до сих пір немає, так як сильно типізованих ідентифікатори так легко програмувати з в домені), або якщо я не знаходжу відповіді швидко я міг би використовувати псевдонім , як запропонований Девідом Брауном - Micrososft нижче ( StackOverflow .com / a / 60155275/1155847 ). Поки що добре для інших випадків використання, колекцій та прихованого поля в EF Core, жодних помилок, тому я вважав, що це дивно, оскільки в іншому випадку у мене є хороший досвід роботи з продуктом.
Ів Шельпе

Це само по собі не дурно, але рідко можна сказати, що НЕ орма, яку я коли-небудь бачив, підтримує це, і EfCore настільки поганий, що зараз я працюю над його видаленням і поверненням до Еф (не ядро), тому що мені потрібно відправляти. Для мене EfCore 2.2 працював краще - 3.1 є 100% непридатним, як і будь-яка прогноза, що я використовую результати в поганому sql або "ми більше не оцінюємо сторону клієнта", навіть якщо - 2.2 чудово оцінили на сервері. Тож я б не очікував, що вони витратять час на подібні речі - в той час, як їх основні функції будуть порушені. github.com/dotnet/efcore/isissue/19830#issuecomment-584234667 для більш детальної інформації
TomTom

EfCore 3.1 зламаний, є причини, чому команда EfCore вирішила більше не оцінювати сторону клієнта, вони навіть видають попередження про це в 2.2, щоб підготувати вас до майбутніх змін. Щодо цього, я не бачу, що ця конкретна річ порушена. Щодо інших речей, які я не можу коментувати, я бачив проблеми, але міг їх розробити без будь-яких витрат на парфу. З іншого боку, на останніх 3-х проектах, які я робив для виробництва, 2 з них базувались на Dapper, на одному Ef ... Можливо, я маю на меті пройти шлях дапера для цього, але перемагає мета легкого входу для нових розробників :-)... Побачимо.
Ів Шельпе

Проблема полягає у визначенні того, що таке оцінка сервера. Вони навіть дують на дуже прості речі, які працювали бездоганно. Виконував функціонал, поки не застосував. Ми просто видаляємо EfCore і повертаємося до EF. EF + третя сторона для глобального lfiltering = працює. Проблема з dapper полягає в тому, що я дозволяю кожному сложному користувачеві, який вирішив LINQ - я повинен перекласти це з bo в серверний запит. Працював в Ef 2.2, зараз повністю накладений на роботу.
TomTom

Гаразд, я зараз прочитав цей github.com/dotnet/efcore/isissue/19679#issuecomment-583650245 ... я бачу, що ви маєте на увазі Яку сторонніх лайф ви використовуєте тоді? Чи можете ви перефразувати те, що ви сказали про Dapper, оскільки я не зрозумів, що ви маєте на увазі. Для мене це спрацьовувало, але це були проекти, які були недостатньо ключовими, на команді було лише 2 диски - і багато ручної котлоавтобуси, щоб написати, щоб вона працювала ефективно, звичайно ...
Ів Шелп
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.