Почти каждый разработчик программного обеспечения работал с СУБД, по крайней мере каждый слышал о них. В мире существует множество способов для работы с базами данных и один из них - это ORM (англ. Oblect-Relational Mapping). Для разработчиков приложений, особенно бизнес-приложений, различного рода реализации данного способа стали в прямом смысле "спасательным кругом" в грубом мире работы с базами данных. Ещё начиная с .Net Framework компания Microsoft кидала такой круг разработчикам, который носил название Entity Framework (EF). И теперь, в .NET есть кроссплатформенная реализация старенького EF - Entity Framework Core (EF Core).
В EF Core существует несколько способов конфигурирования сущностей, все они рассмотрены в моей предыдущей статье - https://habr.com/ru/post/649647/. Лучший из них на мой взгляд - это реализация IEntityTypeConfiguration<>. Он позволяет отделить модель предметной области от хранилища, сделать структуру проекта более упорядоченной, а само решение более гибким. Далее по статье мы рассмотрим все преимущества данного способа. Итак, пришло время на не реальном реальном проекте разобраться наконец с этим способом описания отношений полей и сущностей.
Сделаем конфигурацию для магазина, который занимается продажей автомобилей. База данных у нас уже существует, нужно только "подружить" с ней наше решение на .NET. Будем считать, что структуру нашей будущей БД разрабатывал вменяемый человек, который соблюдал элементарные правила именования полей и т.п.
Имеем несколько таблиц Cars, EquipmentOptions, Makes, Models. Все они имеют поля, которые являются системными, имеют один и тот же тип и называются одинаково. Это поля Id, CreatedDateTime, UpdatedDateTime. Для описания этих полей, мы создадим базовую модель для будущих сущностей BaseEntity:
public class BaseEntity
{
public virtual Guid Id { get; set; }
public virtual DateTime CreatedDateTime { get; set; }
public virtual DateTime? UpdatedDateTime { get; set; }
}После этого мы уже можем не описывать эти три поля в будущих моделях сущностей. Далее приводится листинг сущностей вышеприведенных таблиц.
CarEntity
public class CarEntity : BaseEntity
{
public virtual string Vin { get; set; }
public virtual string EngineNum { get; set; }
public virtual string ChassisNum { get; set; }
public virtual string BodyNum { get; set; }
public virtual Guid EquipmentVariantId { get; set; }
public virtual EquipmentVariantEntity EquipmentVariant { get; set; }
}MakeEntity
public class MakeEntity : BaseEntity
{
public string Name { get; set; }
public virtual EquipmentVariantEntity EquipmentVariant { get; set; }
public virtual ICollection<ModelEntity> Models { get; set; }
}ModelEntity
public class ModelEntity : BaseEntity
{
public virtual string Name { get; set; }
public virtual Guid MakeId { get; set; }
public virtual MakeEntity Make { get; set; }
public virtual EquipmentVariantEntity EquipmentVariant { get; set; }
}EquipmentVariantEntity
public class EquipmentVariantEntity : BaseEntity
{
public virtual string Engine { get; set; }
public virtual Guid ModelId { get; set; }
public virtual ModelEntity Model { get; set; }
public virtual ICollection<CarEntity> Cars { get; set; }
}Как видим, модели наши чисты и намерения наши светлы. Приступим к конфигурации сущностей. Подобно выделению базовой сущности - выделим базовую конфигурацию BaseEntityConfiguration<TEntity>:
internal class BaseEntityConfiguration<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : BaseEntity
{
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.HasKey(k => k.Id);
builder.Property(p => p.Id).HasColumnName("Id");
builder.Property(p => p.CreatedDateTime).HasColumnName("CreatedDateTime").IsRequired();
builder.Property(p => p.UpdatedDateTime).HasColumnName("UpdatedDateTime");
}
}Далее для каждой сущности создадим также конфигурации, но они уже будут расширять нашу базовую конфигурацию. Область видимости мы намеренно делаем internal, т.к. вне проекта, предназначенного для работы с БД никто не должен знать этих деталей.
Ниже листинг данных конфигураций.
CarEntityConfiguration
internal class CarEntityConfiguration : BaseEntityConfiguration<CarEntity>
{
public override void Configure(EntityTypeBuilder<CarEntity> builder)
{
base.Configure(builder);
builder.ToTable("Cars");
builder.Property(p => p.Vin)
.HasMaxLength(64)
.IsRequired();
builder.Property(p => p.BodyNum)
.HasMaxLength(64);
builder.Property(p => p.ChassisNum)
.HasMaxLength(64);
builder.Property(p => p.EngineNum)
.HasMaxLength(64)
.IsRequired();
builder.Property(p => p.EquipmentVariantId)
.HasColumnName("EquipmentVariantId")
.IsRequired();
builder.HasOne(o => o.EquipmentVariant)
.WithMany(m => m.Cars)
.HasForeignKey(fk => fk.EquipmentVariantId)
.IsRequired();
}
}MakeEntityConfiguration
internal class MakeEntityConfiguration : BaseEntityConfiguration<MakeEntity>
{
public override void Configure(EntityTypeBuilder<MakeEntity> builder)
{
base.Configure(builder);
builder.ToTable("Makes");
builder.Property(p => p.Name)
.HasColumnName("Name")
.HasMaxLength(256)
.IsRequired();
builder.HasMany(m => m.Models)
.WithOne(o => o.Make)
.HasForeignKey(fk => fk.MakeId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
}
}ModelEntityConfiguration
internal class ModelEntityConfiguration : BaseEntityConfiguration<ModelEntity>
{
public override void Configure(EntityTypeBuilder<ModelEntity> builder)
{
base.Configure(builder);
builder.ToTable("Models");
builder.Property(p => p.Name)
.HasColumnName("Name")
.HasMaxLength(256)
.IsRequired();
builder.Property(p => p.MakeId)
.HasColumnName("MakeId")
.IsRequired();
}
}EquipmentVariantEntityConfiguration
internal class EquipmentVariantEntityConfiguration : BaseEntityConfiguration<EquipmentVariantEntity>
{
public override void Configure(EntityTypeBuilder<EquipmentVariantEntity> builder)
{
base.Configure(builder);
builder.ToTable("EquipmentOptions");
builder.Property(p => p.Engine)
.HasColumnName("Engine")
.HasMaxLength(20)
.IsRequired();
builder.Property(p => p.ModelId)
.HasColumnName("ModelId")
.IsRequired();
builder.HasOne(o => o.Model)
.WithOne(o => o.EquipmentVariant)
.HasForeignKey<EquipmentVariantEntity>(fk => fk.ModelId)
.IsRequired();
}
}Вот и всё, конфигурация сущностей произведена. На первый взгляд кажется, что такой подход это оверхед - куча классов, лишняя работа, мы же не индусы
