关于实体框架:如何在 ASP.NET Identity 2 中使用 TPH (Table Per Hierarchy)

How to use TPH (Table Per Hierarchy) with ASP.NET Identity 2

我在 MVC5 项目中使用 ASP.NET Identity 2,有两种类型的用户类,分别称为 StudentCoordinator,如下所示。另一方面,我尝试遵循 TPH(按层次结构表)方法,以便为两种类型的用户使用相同的实体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Student : ApplicationUser
{
    public int? Number { get; set; }
}


public class Coordinator : ApplicationUser
{
    public string Room { get; set; }
}


public class ApplicationUser : IdentityUser<int, ApplicationUserLogin,
     ApplicationUserRole, ApplicationUserClaim>, IUser<int>
{
    //Custom Properties
    public string Name { get; set; }

    public string Surname { get; set; }

    //code omitted for brevity
}

由于 ApplicationUser 已经继承自 IdentityUser,我没有将其创建为 abstract 类,也没有将其添加到我的 DbContext 中,如下所示:

1
public DbSet<ApplicationUser> ApplicationUsers { get; set; }

另一方面,还有另一个实体具有 Student(不是 ApplicationUser)作为导航属性,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Grade
{
    [Key]
    public int Id { get; set; }

    public int Grade { get; set; }

    //Navigation properties ===========================
    public virtual Experiment Experiment { get; set; }

    public virtual Student Student { get; set; }

    //public virtual ApplicationUser User { get; set; }
    //=================================================
}

}

但是,当将 DbSet 添加到 DbContext 时,我遇到了"不支持每种类型的多个对象集。对象集"ApplicationUsers"和"Users"都可以包含类型为"Xxxx.ApplicationUser"的实例\\'。" 错误。那么,关于上述方法是否有任何错误?我应该在 DbContext 中使用 DbSet 并且应该将 ApplicationUser 作为导航属性添加到 Grade 实体吗?任何帮助将不胜感激...


你应该可以在不添加 ApplicationUsers DbSet 到你的 DbContext 的情况下做到这一点,就像这样:

1
2
3
4
5
6
7
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public DbSet<Student> Students { get; set; }
    public DbSet<Coordinator> Coordinators { get; set; }
    public DbSet<Grade> Grades { get; set; }
    ...
}

在你的 Grade 实体的导航属性中使用 Student 类型是可以的,你不应该使用 ApplicationUser.

检查您的数据库是否与您的模型同步。你在使用 EF 迁移吗?迁移文件中的表应如下所示:(注意 AspNetUsers 表中的 Discriminator 字段,以及 NumberRoom 字段。这些是 TPH 应用于表创建的证明。此外,没有StudentCoordinator 的表已包含在迁移中。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
        CreateTable(
           "dbo.AspNetUsers",
            c => new
                {
                    Id = c.String(nullable: false, maxLength: 128),
                    Name = c.String(),
                    Surname = c.String(),
                    Email = c.String(maxLength: 256),
                    EmailConfirmed = c.Boolean(nullable: false),
                    PasswordHash = c.String(),
                    SecurityStamp = c.String(),
                    PhoneNumber = c.String(),
                    PhoneNumberConfirmed = c.Boolean(nullable: false),
                    TwoFactorEnabled = c.Boolean(nullable: false),
                    LockoutEndDateUtc = c.DateTime(),
                    LockoutEnabled = c.Boolean(nullable: false),
                    AccessFailedCount = c.Int(nullable: false),
                    UserName = c.String(nullable: false, maxLength: 256),
                    Room = c.String(),
                    Number = c.Int(),
                    Discriminator = c.String(nullable: false, maxLength: 128),
                })
            .PrimaryKey(t => t.Id)
            .Index(t => t.UserName, unique: true, name:"UserNameIndex");

        CreateTable(
           "dbo.Grades",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    GradeValue = c.Int(nullable: false),
                    Student_Id = c.String(maxLength: 128),
                })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.AspNetUsers", t => t.Student_Id)
            .Index(t => t.Student_Id);

enter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    public void Test()
    {
        using (var context = new ApplicationDbContext())
        {
            var student = new Student
            {
                Id ="12345",
                Name ="John",
                Number = 123,
                UserName ="Johnny",
                Email ="[email protected]"
            };
            context.Students.Add(student);
            context.SaveChanges();
        }

        using (var context = new ApplicationDbContext())
        {
            var student = context.Students.Find("12345");
            var grade = new Grade { Id = 333, GradeValue = 90, Student = student };
            context.Grades.Add(grade);
            context.SaveChanges();
        }
    }

一些问题:

  • "多个对象集"错误消息说明了"对象集\\'ApplicationUsers\\'和\\'Users\\'"。这个 \\'Users\\' 对象集来自哪里,你有一个用户 DbSet 吗?尝试删除它。或者您可能使用 DbSet 的类型参数进行了复制粘贴错误,类似于编写 public DbSet<wrongType> GoodTypeName(它发生了)。

  • 我对这个声明有疑问:

    1
    2
    public class ApplicationUser : IdentityUser<int, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>, IUser<int>
    { ... }

    所以我用了这个(基类中没有类型参数):

    1
    2
    public class ApplicationUser : IdentityUser
    { ... }

    似乎带有 Identity 的 MVC 项目的默认实现期望 ApplicationUser 在没有类型参数的情况下扩展 IdentityUser 的实现。否则会在多个地方出现编译错误,例如在声明 ApplicationContext 并尝试使用 ApplicationUser 作为基类 IdentityDbContext 的类型参数时。

  • 如果您使用带有类型参数的基类,我认为 TKey 类型参数应该是 String 而不是 int,除非您在自定义实体中更改了 User.Id 属性类型。也许您因此而遇到错误?在数据库中,这些实体的 Id 字段的类型为 varchar.

  • 我将 Grade 属性的名称更改为 GradeValue(Grade 不是有效的属性名称,因为它是类的名称,我遇到了编译错误)。

如果你需要它,我可以发送我用来测试这个的虚拟解决方案的代码。