Заменить класс сущности во время выполнения в Entity Framework Core - PullRequest
0 голосов
/ 15 сентября 2018

Мы следуем принципам доменного дизайна в нашей модели, комбинируя данные и поведение в классах объектов и объектов-значений. Нам часто приходится настраивать поведение наших клиентов. Вот простой пример, когда клиент хочет изменить форматирование 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 перейдет в отдельную пользовательскую сборку, которая предоставляется только заказчику, который хочет внести изменения, поэтому они получат стандартную сборку плюс пользовательскую сборку. Мы не будем создавать отдельную сборку всего проекта, чтобы приспособить настройку.

Ответы [ 3 ]

0 голосов
/ 15 сентября 2018

Я бы не стал использовать отдельный класс, если ваши данные хранятся в базовом объекте независимо. То, что я хотел бы сделать, это использовать 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
    }   
}
0 голосов
/ 16 сентября 2018

Исходя из всего, что вы предоставили, я считаю, что создание еще одного 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}";
}

Предупреждение: по какой-то причине тест не пройден с использованием провайдера в памяти.Поэтому вы можете подумать об этом, если вы или ваш клиент используете для тестирования оперативную память.Это может быть ошибкой в ​​самом EF Core, поскольку он отлично работает с sqlite-памятью, как показано.

0 голосов
/ 15 сентября 2018

У меня была похожая задача и в моем проекте.Есть способы перехватчика, чтобы сделать это, но есть две проблемы:

  1. Объект выйдет из синхронизации с прокси
  2. Это не архитектурно разумный способ сделать вещи.

Итак, я закончил преобразование объектов в требуемые типы, используя AutoMapper .Есть и другие библиотеки, которые могут делать то же самое.Таким образом, в каждом домене вы можете легко получить необходимый объект.

Возможно, это не то, что вы просили, но я надеюсь, что это поможет вам.

...