1

我正在尝试在具有继承层次结构的实体之间配置一对一的关系。

对于示例,让我们考虑以下第一个继承链:

[Table("A")]
public abstract class A
{
    public Guid ID { get; set; }
    ...
}

[Table("AA")]
public class AA : A
{
    ...
}

[Table("AB")]
public class AB : A
{
    ...
}

然后,考虑第二个继承链:

[Table("B")]
public abstract class B
{
    public Guid ID { get; set; }
}

[Table("BA")]
public class BA : B
{
    ...
}

[Table("BB")]
public class BB : B
{
    ...
}

在 AA 和 BA 之间添加一对一的关系,以 AA 作为主要实体:

[Table("AA")]
public class AA : A
{
    ...
    public BA BAChild { get; set; }
    ...
}

[Table("BA")]
public class BA : B
{
    ...
    public AA Parent { get; set; }
    ...
}

public class AAConfiguration : EntityTypeConfiguration<AA>
{
    public AAConfiguration()
    {
        this.HasRequired(o => o.BAChild)
            .WithRequiredPrincipal(o => o.Parent);
    }
}

在 AB 和 BB 之间添加一对一的关系,以 AB 作为主要实体:

[Table("AB")]
public class AB : A
{
    ...
    public BB BBChild { get; set; }
    ...
}

[Table("BB")]
public class BB : B
{
    ...
    public AB Parent { get; set; }
    ...
}

public class ABConfiguration : EntityTypeConfiguration<AB>
{
    public ABConfiguration()
    {
        this.HasRequired(o => o.BBChild)
            .WithRequiredPrincipal(o => o.Parent);
    }
}

我还希望 EF 为实体 A 和 B 生成表,因此我添加并注册了以下空 EntityTypeConfiguration :

public class AConfiguration : EntityTypeConfiguration<A>
{

}

public class BConfiguration : EntityTypeConfiguration<B>
{

}

如果您像这样运行代码,您将在创建索引期间遇到错误(升级到实体框架 4.3.1 后看到未处理的异常

所以让我们做一些棘手的代码并注册一个从 SqlServerMigrationSqlGenerator 派生的自定义 MigrationSqlGenerator 以避免根据我的业务命名规则创建索引:

public class Configuration : DbMigrationsConfiguration<DataContext>
{
    public Configuration()
    {
        ...
        this.SetSqlGenerator("System.Data.SqlClient", new CustomSqlServerGenerator());
        ...
    }
}

public class CustomSqlServerGenerator : SqlServerMigrationSqlGenerator
{
    protected override void Generate(CreateIndexOperation createIndexOperation)
    {
        if (createIndexOperation.Columns.Count() == 1 && createIndexOperation.Columns.Any(o => o == "ID"))
            return;

        base.Generate(createIndexOperation);
    }
}

public class DataContext : DbContext
{
    static DataContext()
    {
        Database.SetInitializer<DataContext>(new MigrateDatabaseToLatestVersion<DataContext, Configuration>());
    }

    ...
}

所以现在是时候生成数据库了,我使用以下代码:

...
DataContext dataContext = new DataContext();
dataContext.Database.Initialize(true);
...

现在,如果您查看生成的数据库,您将看到表 BA 和 BB 都有表 AB 的外键,而 BA 和 AA 之间没有外键!?!

我可能遗漏了一些东西,但我看不出这个样本有什么问题。

可以做些什么来正确生成数据库?

4

1 回答 1

1

在对反射器进行一些深入搜索之后,这种行为似乎是一个 EntityFramework 错误。

我在以下函数中的 System.Data.Entity.ModelConfiguration.Configuration.Mapping.ForeignKeyPrimitiveOperations 中找到了问题:

    private static void UpdatePrincipalTables(DbDatabaseMapping databaseMapping, DbTableMetadata toTable, bool removeFks, EdmAssociationType associationType, EdmEntityType et)
    {
        ...
    }

当 EntityFramework 加载模型时,将使用基本类型加载 DbDatabaseMapping 实例,并将外键约束添加到基本类型元数据中。EntityFramework 还将 EdmAssociationType 的实例存储在外键约束的注释中,以维护生成正确约束所需的所有信息。

稍后,当在 DbDatabaseMapping 实例中添加和配置派生类型时,EntityFramework 会尝试更新关联的相关端以移动派生表上的外键约束。为了识别外键约束以更新以下选择器,其中 tableInfo.Value 是一个 IEnumerable,其中包含外键的依赖列:

fk => fk.DependentColumns.SequenceEqual<DbTableColumnMetadata>(tableInfo.Value);

不幸的是,在我的例子中,对于 AA 和 BA 之间的关系以及 AB 和 BB 之间的关系,使用了“ID”列。

因此,它们都第一次更新,用 AA 元数据替换主表元数据,第二次用 AB 元数据替换。

结果,两个外键都是以 AB 作为主表生成的!

我认为在函数参数中传递的 EdmEntityType 应该在选择器中使用,以便与存储在外键约束注释中的关系端的实体类型进行比较。

于 2012-06-29T15:21:37.197 回答