C# 在实体框架核心中的运行时替换实体类

C# 在实体框架核心中的运行时替换实体类,c#,entity-framework-core,domain-driven-design,C#,Entity Framework Core,Domain Driven Design,我们在模型中遵循领域驱动的设计原则,将实体和值对象类中的数据和行为结合起来。我们经常需要为客户定制行为。下面是一个简单的示例,其中客户希望更改FullName的格式: public class Person { public string FirstName { get; set; } public string LastName { get; set; } // Standard format is Last, First public virtual stri

我们在模型中遵循领域驱动的设计原则,将实体和值对象类中的数据和行为结合起来。我们经常需要为客户定制行为。下面是一个简单的示例,其中客户希望更改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; }
公共虚拟数据库集人物{get;set;}
在运行时,需要实例化PersonCustom类来代替Person类。当我们的代码负责实例化实体时,例如添加一个新人时,这不是问题。可以将IoC容器/工厂配置为用Person类替换Person类。但是,在查询数据时,EF负责实例化实体。在查询context.People时,是否有某种方法可以配置或拦截实体创建,以便在运行时用PersonCustom替换Person类

我已经在上面使用了继承,但是如果它起作用的话,我可以实现一个IPerson接口。无论哪种方式,您都可以假定两个类中的接口相同


编辑:这是如何部署的更多信息。。。Person类将是面向所有客户的标准构建的一部分。PersonCustom将进入一个单独的自定义程序集,该程序集只提供给希望更改的客户,因此他们将获得标准构建和自定义程序集。我们不会为整个项目创建单独的构建来适应定制。

我的项目中也有类似的任务。有拦截器方法可以做到这一点,但有两个问题:

  • 对象将与代理不同步
  • 这在体系结构上不是一种合理的做事方式
  • 因此,我最终使用将对象转换为所需的类型。还有其他图书馆也可以做同样的事情。这样,在每个域中,您都可以轻松获得所需的对象


    这可能不是您所要求的,但我希望这会对您有所帮助。

    我的项目中也有类似的任务。有拦截器方法可以做到这一点,但有两个问题:

  • 对象将与代理不同步
  • 这在体系结构上不是一种合理的做事方式
  • 因此,我最终使用将对象转换为所需的类型。还有其他图书馆也可以做同样的事情。这样,在每个域中,您都可以轻松获得所需的对象


    这可能不是您要求的,但我希望这会对您有所帮助。

    如果您的数据存储在基本实体中,我不会尝试使用单独的类。我要做的是使用一个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
        }   
    }
    

    如果数据存储在基本实体中,我不会尝试使用单独的类。我要做的是使用一个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
        }   
    }
    

    基于您提供的一切,我相信为其他客户创建另一个
    DbContext
    最适合您的场景

    下面的代码显示了一个测试,我使用
    PersonContext
    添加
    Person
    实体。然后使用
    PersonCustomContext
    读取相同的实体,但被实例化为
    PersonCustom

    另一个关注点是
    PersonCustom
    键的显式配置,指向在其基本类型
    Person
    中定义的
    PersonId

    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}";
    }
    
    使用Microsoft.Data.Sqlite;
    使用Microsoft.EntityFrameworkCore;
    使用System.Linq;
    使用Xunit;
    公开课考试
    {
    [事实]
    public void PersonCustomContext\u可以\u获取\u PersonCustom()
    {
    var connection=newsqliteconnection(“数据源=:内存:”);
    connection.Open();
    var options=new DbContextOptionsBuilder()
    .UseSqlite(连接)
    .选择;
    使用(var ctx=新PersonContext(选项))
    ctx.Database.recreated();
    使用(var ctx=新PersonContext(选项))
    {
    添加(新的人{FirstName=“John”,LastName=“Doe”});
    ctx.SaveChanges();
    }
    使用(var ctx=newpersoncustomcontext(选项))
    {
    Assert.Equal(“johndoe”,ctx.People.Single().FullName);
    }
    }
    }
    公共类PersonContext:DbContext
    {
    公共PersonContext(DbContextOptions选项):基本(选项){}
    公共数据库集人物{get;set;}
    }
    公共类PersonCustomContext:DbContext
    {
    public PersonCustomContext(DbContextOptions选项):基(选项){}
    公共数据库集人物{get;set;}
    模型创建时受保护的覆盖无效(ModelBuilder ModelBuilder)
    {
    实体(生成器=>
    {
    builder.HasKey(p=>p.PersonId);
    });
    }
    }
    公共阶层人士
    {
    公共int PersonId{get;set;}
    公共字符串名{get;set;}
    公共字符串L