【问题标题】:Inheritance in DbSet<T> EntityFramework for similar tablesDbSet<T> EntityFramework 中类似表的继承
【发布时间】:2017-09-13 13:32:08
【问题描述】:

我想为类似的表做继承。

例如,假设我们有两个表:TeachersStudents,它们都可以从基类Human 派生。

是否可以使用 EntityFramework 编写通用任务并避免重复代码?像这样适用于DbSet&lt;Student&gt;DbSet&lt;Teacher&gt; 的函数:

void IncreaseAge(DbSet<Human> humans, int id)
{
   //
}

更重要的是,一个通用的添加函数到 db,伪代码:

void AddHuman({...}, string name, int age)
{
   // add a human to related table
}

任何帮助将不胜感激。

【问题讨论】:

标签: c# entity-framework


【解决方案1】:

使用带有通用参数的扩展:

void IncreaseAge<T>(DbSet<T> entities, int id) where T: Human
{
   var current = entities.Find(id);
   current.Age++;
   // SaveChanges() in the context
}

如果您的学生从 Human 类继承 Age 属性。这段代码应该可以完美运行。

已编辑

此外,您可以对添加应用相同的技术

void Insert<T>(DbSet<T> entities, string name, int age) where T: new(), Human
{
   entities.Add(new T{ Name = name, Age = age });
   // SaveChanges() in the context
}

希望对您有所帮助!

【讨论】:

  • 我更新了我的问题,泛型添加到数据库怎么样?您的代码不适用于添加。
  • @mojtaba357 首先,您不应该在回答后修改问题,而是发布另一个问题。其次,您不能仅仅因为您没有提供任何代码(即使对于原始问题)而说代码不起作用。
【解决方案2】:

一个 DbSet 代表所有个实体。您通常不会将其传递给函数。而是在您的 DbContext 中考虑这样的方法:

    public void IncreaseAge(IQueryable<Human> humans) 
    {            
        foreach( var h in humans)
        {
            h.Age++;
        }
        SaveChanges();
    }

然后,您可以传入一个查询,该查询指定您希望对其进行操作的一组教师或学生。例如:

     db.IncreaseAge(db.Teachers.Where(t => t.Age == 47));

【讨论】:

  • 这句话是错误的,db.IncreaseAge()增加年龄没有在DB上定义
  • @johnny5 “数据库上没有定义增加年龄”肯定是的,因为那是我放的地方。但它可能在其他地方。如果在 DbContext 之外,它可能会占用(或返回)IList,因为您在调用后需要集合。
  • 或者只是扩展方法IncreaseAge(this IQueryable&lt;Human&gt; humans) ...然后看起来像db.Teachers.Where(t =&gt; t.Age == 47).IncreaseAge();
【解决方案3】:

我猜你在实体框架中使用继承时已经阅读了various strategies。实体框架的好处在于它隐藏了使用的继承策略。

由于链接中描述了三种继承策略,因此无需描述为此所需的fluent API。我只会写你最终会得到的类,以及如何对学生、教师和公共基类 Persons 进行查询。

此外,我将描述正确选择正确继承策略所需的一些注意事项。

所以你有StudentTeachers,它们都派生自Person

abstract class Person
{
    public string Name {get; set}
    public Gender Gender {get; set;}
    public DateTime Birthday {get; set;}
}

public class Teacher : Person
{
    public int Id {get; set;}
    ...
}

public class Student : Person
{
    public int Id {get; set;}
    ...
}

还有 DbContext:

public class MyDbContext : DbContext
{
     public DbSet<Teacher> Teachers {get; set;}
     public DbSet<Student> Students {get; set;}
}

现在,只要您有教师或学生的 IQueryable,您还可以使用 Person 的所有属性,而无需做任何特别的事情。

var maleTeachers = myDbContext.Teachers
    .Where(teacher => teacher.Gender == Gender.Male);
var youngStudents = myDbcontext.Students
    .Where(student => student.Birthday > new Datetime(2000, 1, 1);

如果您必须查询所有人员,则必须连接学生和教师。步步为营:

IQueryable<Person> teachers = myDbcontext.Teachers.Cast<Person>();
IQueryable<Person> students = myDbContext.Students.Cast<Person>();
IQueryable<Person> allPersons = teachers.Concat(students);
var result = allPersons.Where(person => ...)
     .Select(person => ...)
     ... etc

当然这可以在一个语句中完成。

决定使用哪种继承策略

在决定继承策略时,请记住您最常执行的查询类型:

  • 查询...的教师或查询...的学生
  • 查询...的人员

如果您最常使用第一个,请考虑Table per concrete class (TPC). 您将有两张桌子,一张供学生使用,一张供教师使用。 Person 属性位于同一个表中。所以表Students会有Person列;表教师也将有人员列。

好处是,如果你问“Students who ...”,只涉及一张桌子。不需要加入。

缺点是,如果您要求“Persons that ...”,则需要将 Teachers 和 Students 表连接起来。

如果您要更频繁地查询 Persons,请考虑使用 creating Table per Type (TPT)。结果将是三个带有外键的表:Teachers、Persons、Student。在询问 Persons 时,只涉及一张桌子。但是,在请求教师时,我们总是需要连接两个表。

即使您选择了最佳继承策略,因为这些是您最常执行的查询类型,您有时也可能需要执行其他类型的查询。

TPC:每个具体类的表

如果您主要要求教师...或学生...您会选择此选项,您最终会得到两张表:一张用于学生,一张用于教师。不会有 Persons 表。

如果您必须偶尔执行 Person 查询,则必须连接两个序列。这是小步骤:

IQueryable<Person> teachers = myDbcontext.Teachers.Cast<Person>();
IQueryable<Person> students = myDbContext.Students.Cast<Person>();
IQueryable<Person> allPersons = teachers.Concat(students);
var result = allPersons.Where(person => ...)
     .Select(person => ...)
     ... etc

当然这可以在一个语句中完成。

如果您必须更频繁地执行此操作,请考虑向您的 DbContext 类添加一个为您执行此操作的属性:

class MyDbcontext : Dbcontext
{
    public DbSet<Teacher> Teachers {get; set;}
    public DbSet<Student> Students {get; set;}

    public IQueryable<Person> Persons
    {
        get
        {
            return this.Teachers.Cast<Person>()
            .Concat(this.Students.Cast<Person>());
        }
    }
}

用法如下:

using (var myDbContext = new MyDbContext(...))
{
     IQueryable<Person> females = myDbcontext.Persons
        .Where(person => person.Gender == Gender.Female);
}

如果您不想污染您的 Dbcontext,请考虑创建一个执行相同操作的扩展函数。见extension functions demystified

static class MyDbContextExtensions
{
    IQueryable<Person> Persons(this MyDbContext dbContext)
    {
        return dbContext.Teachers.Cast<Person>()
            .Concat(dbContext.Students.Cast<Person>());
    }
}

TPT:每种类型的表格

您最终会得到三个表,Student、Teachers、Persons。学生和教师将有一个外键来识别他们是谁。

您可以使用 DbSet 直接查询 Persons。另一方面,如果您只想要...的教师,您只需访问 DbSet。只要您使用继承的 Person 属性之一,Entity 框架就会自动为您执行连接。您不会在代码中看到这一点。因此,即使您不进行连接,内部也会涉及两个表,这可能会降低效率。

所以要小心你选择的继承策略。

【讨论】:

  • 这个答案没有显示任何有用的东西,你为什么不向他们展示如何使用 IQueryable 或编写一个与 IQueryable 一起使用的通用方法,其中 T : Person
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多