只是好奇:Entity Framework 如何确定 m:n 关系中连接 table 的命名?

Just Curious: How does Entity Framework determines the naming of a join table in m:n relationships?

我很好奇 Entity Framework 如何确定 m:n 关系中连接 table 的命名。

上下文:

我有如下学生和课程 class。在 VS 解决方案中进行测试时,EF 生成连接 table 作为 StudentCourses。

我的问题:为什么 EF 生成联接 table 作为 StudentCourses 而不是 CourseStudents? join table 的命名是如何确定的?

示例代码:

public class Student
{
    public Student()
    {
        this.Courses = new HashSet<Course>();
    }

    public int StudentId { get; set; }
    [Required]
    public string StudentName { get; set; }

    public virtual ICollection<Course> Courses { get; set; }
}

public class Course
{
    public Course()
    {
        this.Students = new HashSet<Student>();
    }

    public int CourseId { get; set; }
    public string CourseName { get; set; }

    public virtual ICollection<Student> Students { get; set; }
}

我没能完全找到它,但这是开始。

当您的 DbContext 创建其模型时,OnModelCreating is called. This has a parameter which is a DbModelBuilder

此 DbModelBuilder 有 Conventions of type ConventionsConfiguration。 你会认为我们快到了。唉,ConventionsConfiguration 没有 属性 来访问约定。

如果你看source code of DbModelBuilder, you can see that the conventions are initialize with V2ConventionSet.Conventions. You have to do some deep digging, but then you can find that the V2ConventionSet is derived from V1ConventionsSet。绝大多数约定都在 V1ConventionSet 中。

哎呀,虽然 ConventionSet 中的 Conventions 的名称描述性很强,但我找不到定义联结 table 名称定义方式的 ​​Convention。

使用Reflection,可以看到ConventionConfiguration有一些非public字段

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    var conventions = modelBuilder.Conventions;
    var conventionsType = conventions.GetType();
    var flags = BindingFlags.Instance | BindingFlags.NonPublic;
    var fields = conventionsType.GetFields(flags);
    foreach (var field in fields)
    {
        var conventionValue = field.GetValue(conventions);
        if (conventionValue is IEnumerable sequence)
        {
            foreach (var item in sequence)
            {
                Console.WriteLine(item);
            }
        }
    }
}

这也没什么用,你只能看到Convention是我们在V2ConventionSet中找到的。也许如果您检查这些约定,您可能会发现结合这些名称的约定。也许是定义复数名称的那个?

不管怎样,你可以添加自己的约定,看看会发生什么:

public class JunctionTableConvention : IStoreModelConvention<AssociationType>
{
    public void Apply(AssociationType item, DbModel model)
    {
        var associations = model.ConceptualToStoreMapping.AssociationSetMappings;

        foreach (var association in associations)
        {
            var associationSetEnds = association.AssociationSet.AssociationSetEnds;
            association.StoreEntitySet.Table = String.Format("{0}_{1}",
                GetTableName(associationSetEnds[0].EntitySet.ElementType),
                GetTableName(associationSetEnds[1].EntitySet.ElementType));
        }
    }

    private string GetTableName(EntityType type)
    {
        var result = System.Text.RegularExpressions.Regex
            .Replace(type.Name, ".[A-Z]", m => m.Value[0] + "_" + m.Value[1]);
        return result.ToLower();
    }
}

在 OnModelCreating 中添加此约定:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Conventions.Add(new JunctionTableConvention());
    ...
}

在你的程序中:

static void Main(string[] args)
{
    using (var dbContext = new SchoolDbContext())
    {
        dbContext.Database.Initialize(true);
    }
}

您的调试器将显示调用了 OnModelCreating,其中添加了联结 table 约定。在 OnModelCreating returns 之后,调用 JunctionModel.Apply。

我有一个学生和老师之间的多对多。 AssociationType 是 {CodeFirstDatabaseSchema.Teacher_Students_Source}.

我的代码获取 AssociationSetMappings。只有一个这样的映射。 此映射有一个 StoreEntitySet,它有一个字符串 属性 Table,其值为“TeacherStudents”

我的代码获取 AssociationSetEnds 以查找 DbSet 的类型,并为此构造一个漂亮的名称 table。此名称放在 association.StoreEntitySet.Table.

所以其他约定之一已经用“TeacherStudents”填充了这个值。 要找出是哪个约定这样做的,我可以一个一个地删除约定,然后查看名称何时更改。

但我把那个留给你了。

另一种方法当然是查看所有这些约定的参考来源,看看哪个定义了 table 的名称。