【问题标题】:Entity splitting when key column has different names?键列具有不同名称时的实体拆分?
【发布时间】:2012-02-21 22:40:31
【问题描述】:

我正在使用 Entity Framework 4.3.1 Code-First,我需要在两个表之间拆分一个实体。这些表有一个共享的主键,并且是一对一的,但每个表上的列名称不同。

我不控制数据布局,也不能请求任何更改。

例如,SQL 表可以是

这将是我的实体......

public class MyEntity
{
    public int Id {get; set;}
    public string Name {get;set}
    public string FromAnotherTable {get;set;}
}

这是我的映射。

public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
    public MyEntityMapping()
    {
        this.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
        this.Property(e => e.Name).HasColumnName("MyDatabaseName");
        this.Property(e => e.FromAnothertable).HasColumnName("AnotherTableColumn");
        this.Map(m =>
            {
                m.Properties(e =>
                     {
                         e.Id,
                         e.Name
                     });
                m.ToTable("MainTable");
            });
        this.Map(m =>
            {
                m.Properties(e =>
                     {
                         e.Id,
                         e.FromAnotherTable
                     });
                m.ToTable("ExtendedTable");
            });
}

由于它们之间共享的键具有不同的列名,我不确定如何映射它。此映射将编译,但在运行时失败,因为 EF 发出 SQL 来查找“ExtendedTable”表上的“ThePrimaryKeyId”列,该表不存在。

编辑 澄清一下,如果“扩展表”上的 PK 遵循命名约定,我上面定义的内容可以(并且确实)起作用。但它没有,我无法更改架构。

基本上,我需要 EF 发出一个类似的 SQL 语句

SELECT
    [e1].*,   /*yes, wildcards are bad. doing it here for brevity*/
    [e2].*
FROM [MainTable] AS [e1]
INNER JOIN [ExtendedTable] AS [e2]  /*Could be left join, don't care. */
    ON  [e1].[ThePrimaryKeyId] = [e2].[NotTheSameName]

但它似乎唯一想要发出的是

 SELECT
        [e1].*,
        [e2].*
    FROM [MainTable] AS [e1]
    INNER JOIN [ExtendedTable] AS [e2]
        ON  [e1].[ThePrimaryKeyId] = [e2].[ThePrimaryKeyId] /* this column doesn't exist */

编辑 在 NSGaga 的建议下,我再次尝试了 1 对 1 的方法。它没有用,但结果如下。 实体

public class MyEntity
{
    public int Id { get; set; }
    public int Name { get; set; }
    public virtual ExtEntity ExtendedProperties { get; set; }
}
public class ExtEntity
{
    public int Id { get; set; }
    public string AnotherTableColumn { get; set; }
    public virtual MyEntity MainEntry { get; set; }
}

这里是映射类

public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
    public MyEntityMapping()
    {
        this.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
        this.Property(e => e.Name).HasColumnName("MyDatabaseName");
        this.ToTable("MainTable");
        this.HasKey(e => e.Id);
        this.HasRequired(e => e.ExtendedProperties).WithRequiredPrincipal(f => f.MainEntry);
    }
}

public class ExtEntityMapping : EntityTypeConfiguration<ExtEntity>
{
    public ExtEntityMapping()
    {
        this.Property(e => e.Id).HasColumnName("NotTheSameName");
        this.Property(e => e.AnotherTableColumn).HasColumnName("AnotherTableColumn");
        this.ToTable("ExtendedTable");
        this.HasKey(e => e.Id);
        this.HasRequired(e => e.MainEntry).WithRequiredDependent(f => f.ExtendedProperties);
    }
}

此设置收到消息

"Column or attribute 'MyEntity_ThePrimaryKeyId' is not defined in 'ExtendedTable'"

将最终的地图线改为

this.HasRequired(e => e.MainEntry).WithRequiredDependent(f => f.ExtendedProperties).Map(m => M.MapKey("NotTheSameName"));

返回此消息

"Each property name in a type must be unique. property name 'NotTheSameName' was already defined."

更改映射键以使用父表中的列MapKey("ThePrimaryKeyId")。返回此消息

"Column or attribute 'ThePrimaryKeyId' is not defined in 'ExtendedTable'"

ExtEntity 类中删除Id 属性会引发错误,因为实体没有定义的键。

【问题讨论】:

  • 啊!所以“ExtendedTable”不在您正在创建的架构中;如果您只是通过 DropCreateDatabaseAlways 吹走并创建一个新数据库,那么就无法映射到不存在的表;唯一的方法是将这个表作为架构的一部分包含在内,或者有一个带有“ExtendedTable”的预设数据库,并使用 EF 4.3 代码迁移来更改数据库而不是创建一个新数据库。
  • 我不确定我是否理解你的意思,但这是一个现有的数据库模式。实际上,这是映射到大型机上的 DB2 数据库。我不希望 EF 尝试删除或创建任何内容。
  • 嗨,乔希,我也遇到了同样的问题,并已将其发布在 EF 论坛here 上,但解决它的运气并不好。您是否收到过任何 EF 团队的消息?
  • 您确定必须将实体“拆分”为两部分吗?只需询问 - 将两个实体(一对一 - 必需:必需)关联在一起是完全可定制的。 map/properties 依赖于匿名类型,并且需要一个指向属性的函数/表达式(这就是它获取名称的方式) - 拆分成两个表是非常“明智”的要求。 EF的这条“大道”一直有点问题。您可以尝试更改迁移脚本以重命名列名,但在这种情况下它闻起来有黑客攻击的味道,不确定它是否有效。
  • 我对 C# 代码有完全的控制权,对数据库的控制权为零。

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


【解决方案1】:

我已经在这个问题上工作了几天,最后我所做的是在映射片段的上下文中中设置 Id 字段的列名。这样,您可以为 Id(或依赖于 Id 的外键)赋予与主表的 Id 不同的名称。

this.Map(m =>
    {
        m.Property(p => p.Id).HasColumnName("NotTheSameName");
        m.Properties(e =>
             {
                 e.Id,
                 e.FromAnotherTable
             });
        m.ToTable("ExtendedTable");
    });

如果你运行和调试它,你会发现它会给你一些你想要的东西:

[e1].[ThePrimaryKeyId] = [e2].[NotTheSameName]

【讨论】:

  • 只是好奇。您使用的是什么版本的 EF?
  • @Josh 最新6.1.3
  • @Josh,打算将其标记为答案,但刚刚看到原始问题是针对 EF 4.3。希望您能够升级版本并解决您的问题!
【解决方案2】:

我找不到任何明确说明列名在两个表中必须相同的内容;但我也找不到任何说它没有的东西,或者解释你将如何映射那个场景。我能找到的每个示例在两个表中都有同名的键。在我看来,这是 DbContext 设计中的一个漏洞。

【讨论】:

  • 是的,我现在也是这样。
【解决方案3】:

将 HasColumnName 移动到映射内:

this.Property(e => e.FromAnothertable).HasColumnName("AnotherTableColumn");
this.Map(m =>
    {
        m.Properties(e => new
             {
                 e.Id,
                 e.Name
             });
             m.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
             m.Property(e => e.Name).HasColumnName("MyDatabaseName");

           m.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
        m.ToTable("MainTable");
    });
this.Map(m =>
    {
        m.Properties(e => new
             {
                 e.Id,
                 e.FromAnotherTable
             });
        m.ToTable("ExtendedTable");
    });
}

【讨论】:

  • 几乎,他为 Id 列使用了两个名称,第一个映射片段有一个 Id 名称,但没有第二个。使用 m.P​​roperty(p => p.Id).HasColumnName("NotTheSameName"); 在第二个映射片段中为 Id 命名会解决的:)
【解决方案4】:

这里没有 Visual Studio,但可以使用一对一的方法尝试:

this.HasRequired(e => e.ExtendedProperties).HasConstraint((e, m) => e.Id == m.Id);

更新:
以下是一些可能有帮助的链接(找不到真正的参考链接)

How to declare one to one relationship using Entity Framework 4 Code First (POCO)
Entity Framework 4 CTP 4 Code First: how to work with unconventional primary and foreign key names

【讨论】:

  • 我没有找到像 .HasConstraint() 这样的方法。你能指出我的文档或一些参考吗?谢谢!
  • 看起来 HasConstraint() 在 CTP 中,但不在版本中。没有找到对应的。我使用的是 4.3.1 版本。
  • 顺便说一句,实体拆分将是解决此问题的首选解决方案。一对一是不太理想的选择。我认为 EF 不喜欢它,因为它希望看到另一个表上表示的每个表的 PK,而这是强制执行它的 FK AND PK。
【解决方案5】:

而且只是为了提供(正如我所承诺的那样)一对一(两个实体,两个表)映射,物有所值。
这对我有用,在你的情况下应该......

public class MainTable
{
    public int ThePrimaryKeyId { get; set; }
    public string Name { get; set; }
}
public class ExtendedTable
{
    public int NotTheSameNameID { get; set; }
    public string AnotherTableColumn { get; set; }
    public MainTable MainEntry { get; set; }
}
public class MainDbContext : DbContext
{
    public DbSet<MainTable> MainEntries { get; set; }
    public DbSet<ExtendedTable> ExtendedEntries { get; set; }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<MainTable>()
            .HasKey(x => new { x.ThePrimaryKeyId });

        modelBuilder.Entity<ExtendedTable>()
            .HasKey(x => new { x.NotTheSameNameID });

        // Extended To Main 1 on 1
        modelBuilder.Entity<ExtendedTable>()
            .HasRequired(i => i.MainEntry)
            .WithRequiredDependent();
    }
}

...以及类似...的测试代码

using (var db = new UserDbContext())
{
    foreach (var userid in Enumerable.Range(1, 100))
    {
        var main = new MainTable { Name = "Main" + userid };
        db.MainEntries.Add(main);

        var extended = new ExtendedTable { AnotherTableColumn = "Extended" + userid, MainEntry = main };
        db.ExtendedEntries.Add(extended);
    }
    int recordsAffected = db.SaveChanges();
    foreach (var main in db.MainEntries)
        Console.WriteLine("{0}, {1}", main.Name, main.ThePrimaryKeyId);
    foreach (var extended in db.ExtendedEntries)
        Console.WriteLine("{0}, {1}, {2}, {3}", extended.AnotherTableColumn, extended.NotTheSameNameID, extended.MainEntry.Name, extended.MainEntry.ThePrimaryKeyId);
}

这将创建以下 SQL 脚本、表...

CREATE TABLE [MainTables] (
    [ThePrimaryKeyId] [int] NOT NULL IDENTITY,
    [Name] [nvarchar](4000),
    CONSTRAINT [PK_MainTables] PRIMARY KEY ([ThePrimaryKeyId])
)
CREATE TABLE [ExtendedTables] (
    [NotTheSameNameID] [int] NOT NULL,
    [AnotherTableColumn] [nvarchar](4000),
    CONSTRAINT [PK_ExtendedTables] PRIMARY KEY ([NotTheSameNameID])
)
CREATE INDEX [IX_NotTheSameNameID] ON [ExtendedTables]([NotTheSameNameID])
ALTER TABLE [ExtendedTables] ADD CONSTRAINT [FK_ExtendedTables_MainTables_NotTheSameNameID] FOREIGN KEY ([NotTheSameNameID]) REFERENCES [MainTables] ([ThePrimaryKeyId])

还有一个注释,根据我们上面的讨论...
这不是“分裂” - 但是
(a)代码优先IMO不允许这样的事情(我首先尝试过并且还手动修改了迁移,但它是“内部”所有基于预期的列名相同并且似乎没有办法解决它,因为至少是这个版本的 EF。
(b) 明智的表结构 - 可以使表看起来完全符合您的需要(正如我在使用它将现有的 aspnet 成员表(我无法更改)关联到具有自己的用户的用户表之前所说的那样) -id 指向外部/aspnet 表和 id。
诚然,你不能使用一个 C# 模型类来实现它——但是 C# 方面要灵活得多,如果你可以控制应该产生相同效果的 C#,至少在我看来(就像在测试中一样,你可以随时访问它通过扩展实体,扩展列和主列,它们总是一对一匹配并保持“同步”。
希望这会有所帮助
注意:您不必担心 fk id 等 - 只需始终通过 MainEntry 访问和添加 Main 条目,id-s 就可以了。

编辑:
您还可以执行以下操作,以获得只需要处理一个类的外观(即某种拆分)

public class ExtendedTable
{
    public int NotTheSameNameID { get; set; }
    public string AnotherTableColumn { get; set; }

    public string Name { get { return MainEntry.Name; } set { MainEntry.Name = value; } }
    // public int MainID { get { return MainEntry.ThePrimaryKeyId; } set { MainEntry.ThePrimaryKeyId = value; } }
    internal MainTable MainEntry { get; set; }

    public ExtendedTable()
    {
        this.MainEntry = new MainTable();
    }
}

...并像这样使用它...

var extended = new ExtendedTable { AnotherTableColumn = "Extended" + userid, Name = "Main" + userid };  

...您也可以通过执行 WithRequiredPrincipal 而不是依赖来恢复 fk 的方向。
(如果您需要一对一,所有参考都必须是不带“虚拟”的)
(并且 MainTable 可以在此处设置为“内部”,因此从外部看不到 - 它不能嵌套,因为 EF 不允许 - 被视为 NotMapped)
...嗯,这是我能做的最好的:)

【讨论】:

  • 试过了,结果报错 {"Invalid column name 'ExtendedTable_ThePrimaryKeyId'."}
  • 我已经经历了更多,我认为部分问题是我们正在使用的实体提供程序。默认 SQL 的行为与您描述的一样,但这是来自 IBM 的 DB2 提供程序。
  • 您是否尝试过“清理”数据库、迁移?我正在使用最新的 EF 4.3 - 而且我必须启用迁移 - 每次我通常从重做的项目中删除 db 和迁移(请查看 min 的这篇文章以了解有关 stackoverflow.com/questions/9364750/… 的详细信息)。如果需要,我可以向您发送工作项目 - 只是不确定如何在 SO 中做到这一点
  • 这是一个新事物(对我来说,我看到你提到过) - 仍然不管提供者 - 我认为你的迁移文件应该看起来一样 - 如果你有你应该的列,你可以在那里检查像我的 SQL/表。迁移有点棘手 - 我为此使用 SQL CE,仍然是 MS。
【解决方案6】:

我想建议使用一些这样的数据注释:

MainTable
---------
MainTableId
DatabaseName

ExtendedTable
----------
NotTheSameName
AnotherColumn

public class MainTable
{
 [Key]
 public int MainTableId { get; set; }
 public string DatabaseName { get; set; }

 [InverseProperty("MainTable")]
 public virtual ExtendedTable ExtendedTable { get; set; }
}

public class ExtendedTable
{
 [Key]
 public int NotTheSameName { get; set; }

 public string AnotherColumn { get; set; }

 [ForeignKey("NotTheSameName")]
 public virtual MainTable MainTable { get; set; }
}

【讨论】:

  • 感谢您的建议,但在这种情况下,我们无法选择数据注释。此外,微软表示,Data Annotations 的所有功能都可以通过 Fluent API 实现,而 Fluent API 可以做的事情是注解无法做到的。
  • @Josh - 在我看来,Fluent API 可能会变得乏味并且不像数据注释那样直接读取。这是 API,但反向属性定义相当模糊:msdn.microsoft.com/en-us/library/hh295843(v=vs.103).aspx。不能使用数据注释太糟糕了,它们使使用 EF 更容易阅读和编码。接受的答案“在我看来这是 DbContext 设计中的一个漏洞。”不是真的。使用 fluent api 或数据注释将完成反向属性映射。
  • 我不同意你的观点,即 Fluent API 乏味或不那么直接。我也不同意注释更容易阅读。但我的意见在这方面并不重要。我们的问题是使用数据注释将实体框架依赖项泄漏到实体对象中,这意味着依赖项目也必须具有 EF 引用。通过分离实体映射类,我们将 EF 依赖项隔离到实现 DbContext 并使用映射的项目。此外,我们将相同的实体映射到两个不同的数据存储,这需要不同的映射。
  • 如果您对所提出的问题提供答案,那么我将修改答案。简单地说它是“不正确的”而不给出答案是没有用的。
  • @Josh - 如果您希望只获得该形式的答案,您应该明确说明对流利 api 答案的要求。我演示了一种技术,该技术表明 EF 确实支持这种类型的共享数据,因此可以说“在我看来这是 DbContext 设计中的一个漏洞”。没有支持链接,甚至没有一个例子,这里的答案 is 对其他试图解决这个问题的人来说毫无用处。有关流畅的 api 方法和反对数据注释的论点,请参阅此链接:stackoverflow.com/a/5693427/1026459
【解决方案7】:

看起来它已在 Entity Framework 6 中修复。请参阅此问题 http://entityframework.codeplex.com/workitem/388

【讨论】:

  • 很遗憾,该链接已失效。
【解决方案8】:

我遇到了这个问题,并通过添加 Column 属性来匹配两个列名来解决。 [Key] [Column("Id")] public int GroupId { get; set; }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多