EF TPT继承映射问题具有一对一的关系

时间:2012-06-28 15:17:00

标签: c# entity-framework inheritance ef-code-first

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

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

[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>
{

}

如果你运行这样的代码,你会在索引创建过程中遇到一个错误(见Unhandled Exception after Upgrading to Entity Framework 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之间没有外键!?!

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

如何正确生成数据库?

1 个答案:

答案 0 :(得分: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实例将加载基类型,外键约束将添加到基本类型metadatas。 EntityFramework还在外键约束的注释中存储EdmAssociationType的实例,以维护所有必需的信息以生成正确的约束。

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

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

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

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

因此,两个外键都以AB作为主表生成!

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