В нашей модели мы следуем принципам предметно-ориентированного проектирования, комбинируя данные и поведение в классах объектов сущностей и значений. Нам часто нужно настраивать поведение наших клиентов. Вот простой пример, когда клиент хочет изменить способ форматирования FullName:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
// Standard format is Last, First
public virtual string FullName => $"{LastName}, {FirstName}";
}
public class PersonCustom : Person
{
// Customer wants to see First Last instead
public override string FullName => $"{FirstName} {LastName}";
}
DbSet настроен в DbContext, как и следовало ожидать:
public virtual DbSet<Person> People { get; set; }
Во время выполнения необходимо создать экземпляр класса PersonCustom вместо класса Person. Это не проблема, когда наш код отвечает за создание экземпляра объекта, например, при добавлении нового человека. Контейнер / фабрику IoC можно настроить для замены класса PersonCustom на класс Person. Однако при запросе данных EF отвечает за создание экземпляра объекта. При запросе context.People есть ли способ настроить или перехватить создание объекта, чтобы заменить PersonCustom на класс Person во время выполнения?
Я использовал наследование выше, но вместо этого я мог бы реализовать интерфейс IPerson, если бы это имело значение. В любом случае вы можете предположить, что интерфейс будет одинаковым в обоих классах.
Редактировать: Еще немного информации о том, как это развертывается ... Класс Person будет частью стандартной сборки, которая предоставляется всем клиентам. PersonCustom перейдет в отдельную настраиваемую сборку, которая отправляется только клиенту, который хочет изменения, поэтому они получат стандартную сборку плюс настраиваемую сборку. Мы не будем создавать отдельную сборку всего проекта для адаптации.
@PaoloGo Я добавил в публикацию некоторую информацию о развертывании. Обратите внимание, что класс PersonCustom будет доступен только одному клиенту, которому нужна настройка. Вы предлагаете создать отдельный DbContext для этого клиента, чтобы указать DbSet<PersonCustom> People?
Наследование для этого не подходит. Наследование касается иерархических категорий «вещей», определяющих, что такое является. Просто наличие другого предпочтения формата имени по существу не меняет определения личности. Стратегия ИМО - правильный образец здесь.





У меня была аналогичная задача и в моем проекте. Есть способы сделать это перехватчиками, но есть две проблемы:
Итак, я закончил преобразование объектов в требуемые типы с помощью AutoMapper. Есть и другие библиотеки, которые могут делать то же самое. Таким образом, в каждом домене вы легко сможете получить нужный объект.
Возможно, вы спросили не об этом, но я надеюсь, что это вам поможет.
Все предложения верны, но я думаю, что это лучшее решение для нашего сценария. Спасибо!
Я бы не стал пытаться использовать отдельный класс, если ваши данные в любом случае хранятся в базовом объекте. Что бы я сделал, так это использовать ViewModel или как вы хотите это назвать, и сопоставить (вручную или с помощью одного из множества AutoMapper) вашу сущность с этим. Как правило, я не рекомендую использовать вашу сущность для чего-либо, кроме взаимодействия с базой данных. Вся логика и другие манипуляции должны происходить в отдельном классе, даже если этот класс является копией всех свойств 1: 1 (гораздо легче справиться с изменениями позже, чем выполнять дополнительные миграции или рисковать другим нарушением). изменения).
Краткий пример, чтобы лучше объяснить мою мысль.
//entity
public class Person
{
public string FirstName {get; set;}
public string LastName {get; set;}
}
//base ViewModel
public class PersonModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
// Standard format is Last, First
public virtual string FullName => $"{LastName}, {FirstName}";
}
//customer specific ViewModel
public class CustomerSpecificModel : PersonModel
{
// Standard format is Last, First
public virtual string FullName => $"{FirstName} {LastName}";
}
//Mapping
public class Mapping
{
public void DoSomeWork()
{
var db = new People();
var defaultPerson = db.People.Select(p => new PersonModel
{
FirstName = p.FirstName,
LastName = p.LastName
};
var customerSpecificModel = db.People.Select(p => new CustomerSpecificModel
{
FirstName = p.FirstName,
LastName = p.LastName
}
Console.WriteLine(defaultPerson.First().FullName); //would return LastName, FirstName
Console.WriteLine(customerSpecificModel.First().FullName); //would return FirstName LastName
}
}
DDD способствует объединению данных и поведения в классах сущностей и отходу от Модель анемического домена. Я уже сопоставляю объекты с DTO для отправки по сети, поэтому я надеюсь избежать другого уровня сопоставления.
Исходя из всего, что вы предоставили, я считаю, что создание еще одного DbContext для другого клиента лучше всего подходит для вашего сценария.
В приведенном ниже коде показан тест, в котором я использовал PersonContext для добавления объекта Person. Затем используйте PersonCustomContext для чтения того же объекта, но он создан как PersonCustom.
Другой интересный момент - явная конфигурация ключа PersonCustom, указывающая на ключ PersonId, определенный в его базовом типе Person.
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using Xunit;
public class Tests
{
[Fact]
public void PersonCustomContext_can_get_PersonCustom()
{
var connection = new SqliteConnection("datasource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder()
.UseSqlite(connection)
.Options;
using (var ctx = new PersonContext(options))
ctx.Database.EnsureCreated();
using (var ctx = new PersonContext(options))
{
ctx.Add(new Person { FirstName = "John", LastName = "Doe" });
ctx.SaveChanges();
}
using (var ctx = new PersonCustomContext(options))
{
Assert.Equal("John Doe", ctx.People.Single().FullName);
}
}
}
public class PersonContext : DbContext
{
public PersonContext(DbContextOptions options) : base(options) { }
public DbSet<Person> People { get; set; }
}
public class PersonCustomContext : DbContext
{
public PersonCustomContext(DbContextOptions options) : base(options) { }
public DbSet<PersonCustom> People { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonCustom>(builder =>
{
builder.HasKey(p => p.PersonId);
});
}
}
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual string FullName => $"{LastName}, {FirstName}";
}
public class PersonCustom : Person
{
public override string FullName => $"{FirstName} {LastName}";
}
Предостережение: по какой-то причине тест не проходит с помощью провайдера в памяти. Так что вы можете подумать об этом, если вы или ваш клиент используете для тестирования in-memory. Это может быть ошибка в самом EF Core, поскольку он отлично работает с sqlite inmemory, как показано.
Почему бы вместо этого не использовать
DbSet<PersonCustom> People?