DbSet中的继承<t>类似表的EntityFramework

时间:2017-09-13 13:32:09

标签: c# entity-framework

我想为类似的表做继承。

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

是否可以编写常见任务并避免使用EntityFramework重复代码?这样的函数适用于DbSet<Student>DbSet<Teacher>

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

更重要的是db的泛型添加函数,伪代码:

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

任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:3)

将扩展名与通用参数一起使用:

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

如果您的学生从人类继承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
}

希望这有帮助!

答案 1 :(得分:1)

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

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

然后,您可以传递一个查询,指定您要操作的一组教师或学生。 EG:

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

答案 2 :(得分:0)

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

由于链接中描述了三种继承策略,因此无需描述为此所需的流畅API。我只会写你最终会上的课程,以及如何对学生,教师和普通基础人员进行查询。

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

所以你有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 teachers = myDbcontext.Teachers.Cast();     IQueryable students = myDbContext.Students.Cast();     IQueryable allPersons = teachers.Concat(学生);     var result = allPersons.Where(person =&gt; ...)          。选择(person =&gt; ...)          ......等等。

当然,这可以在一个声明中完成。

使用哪种继承策略?

在决定继承策略时,请记住您最常做的查询:

  • 查询......和查询学生......
  • 的教师
  • 查询人......

如果你经常做第一次,那么考虑Table per concrete class (TPC).你将有两张桌子,一张给学生,一张给老师。 Person属性位于同一个表中。

优点是,如果你要求“学生......”,只涉及一张表。没有必要加入。

缺点是如果你要求“人......”,教师和学生表需要连接。

如果您要更频繁地查询人员,请考虑creating Table per Type (TPT)。结果将是三张外键表:教师,人员,学生。在询问人员时,只涉及一张桌子。但是,在要求教师时,我们总是需要加入两个表格。

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

每个具体课程的TPC表两个表:一个用于学生,一个用于教师,因为您主要查询学生...或教师......没有人员表。

如果你不得不偶尔进行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:每种类型的表格三个表格,学生,教师,人员。内部是学生对人的外键

您可以使用DbSet直接查询人员。如果您只想要教师...,只需访问DbSet。只要您使用其中一个继承的Person属性,Entity框架就会自动为您执行连接。你不会在代码中看到这一点。

所以要注意你选择的继承策略