【问题标题】:Solely mapping existing db to existing POCOs without designer or CodeFirst in Entity Framework 6在 Entity Framework 6 中仅将现有数据库映射到现有 POCO,无需设计器或 CodeFirst
【发布时间】:2014-05-22 14:59:46
【问题描述】:

@slauma 在 cmets 中给出的一个重要事实。所以看看答案和 cmets!

我正在尝试在 EF6 中使用实际与 NHibernate 一起使用的组件。问题是,我有一些带有不同名称主键的 TPT 继承。给出了数据库和 POCO 类,我无法更改它们中的任何一个,因此 CodeFirst 和 EF 设计器都没有问题。

有没有办法将现有 Db 映射到现有 POCO 类,就像您在 NHibernate 中使用这些 .hbm.xml 映射文件一样?

更新:

我遇到的实际问题首先是几个类的TPT映射,其中这些类的主键名称不同,代码优先似乎不支持。

比如:

public class Record
{
  public virtual int Ndx { get; set; }  // table column 'ndx'

  public virtual DateTime CreatedAt { get; set; }  // table column 'created'

  // ... further properties
}

public class Patient : Record
{
  public virtual int RecordNdx {get; set;}  // table column 'record_ndx) with FK => records.ndx

  // ... further properties
}

如前所述,更改属性或列名不是一种选择。

更新二:

这是我的注册码:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Record>()
        .ToTable("record_descriptors", "schema");

    modelBuilder.Entity<Record>()
        .HasKey<int>(e => e.ndx);

    modelBuilder.Entity<Record>()
        .Property(e => e.read_flag)
        .IsFixedLength()
        .IsUnicode(false);

    modelBuilder.Entity<Record>()
        .Property(e => e.row_version)
        .IsFixedLength();

    modelBuilder.Entity<Record>()
        .Property(e => e.update_info)
        .IsUnicode(false);

    modelBuilder.Entity<Patient>()
        .ToTable("patienten", "schema");

    modelBuilder.Entity<Patient>()
        .Property(e => e.mpi)
        .IsUnicode(false);

    modelBuilder.Entity<Patient>()
        .Property(e => e.ndx)
        .HasColumnName("record_ndx");

    modelBuilder.Entity<Patient>()
        .Ignore(r => r.RecordNdx);
}

更新 III

为了测试我使用:

db.patients.First(p => p.Ndx == 6040);

这会产生以下 SQL(由于真实记录和患者类而更广泛):

SELECT 
    [Limit1].[C1] AS [C1], 
    [Limit1].[ndx] AS [ndx], 
    [Limit1].[owner_user_object_ndx] AS [owner_user_object_ndx], 
    [Limit1].[creator_department_user_object_ndx] AS [creator_department_user_object_ndx], 
    [Limit1].[creator_user_user_object_ndx] AS [creator_user_user_object_ndx], 
    [Limit1].[created] AS [created], 
    [Limit1].[read_flag] AS [read_flag], 
    [Limit1].[last_update] AS [last_update], 
    [Limit1].[last_update_user] AS [last_update_user], 
    [Limit1].[last_update_department] AS [last_update_department], 
    [Limit1].[freitext] AS [freitext], 
    [Limit1].[row_version] AS [row_version], 
    [Limit1].[update_info] AS [update_info], 
    [Limit1].[mpi] AS [mpi]
    FROM ( SELECT TOP (1) 
        [Extent1].[ndx] AS [ndx], 
        [Extent1].[mpi] AS [mpi], 
        [Extent2].[owner_user_object_ndx] AS [owner_user_object_ndx], 
        [Extent2].[creator_department_user_object_ndx] AS [creator_department_user_object_ndx], 
        [Extent2].[creator_user_user_object_ndx] AS [creator_user_user_object_ndx], 
        [Extent2].[created] AS [created], 
        [Extent2].[read_flag] AS [read_flag], 
        [Extent2].[last_update] AS [last_update], 
        [Extent2].[last_update_user] AS [last_update_user], 
        [Extent2].[last_update_department] AS [last_update_department], 
        [Extent2].[freitext] AS [freitext], 
        [Extent2].[row_version] AS [row_version], 
        [Extent2].[update_info] AS [update_info], 
        '0X0X' AS [C1]
        FROM  [schema].[patienten] AS [Extent1]
        INNER JOIN [schema].[record_descriptors] AS [Extent2] ON [Extent1].[ndx] = [Extent2].[ndx]
        WHERE 6040 = [Extent1].[ndx]
    )  AS [Limit1]

这是错误的,因为它从[patienten] 中选择[ndx](必须是record_ndx)并尝试加入[ndx]

【问题讨论】:

    标签: c# entity-framework ef-code-first poco entity-framework-6


    【解决方案1】:

    关于关闭this work item at CodePlex 的评论声称自 EF 6 在 TPT 映射中定义父实体和子实体的不同键列名称适用于 Code-First。如果这是真的,那么以下 Code-First 映射应该允许映射您的模型和数据库:

    modelBuilder.Entity<Record>()
        .ToTable("YourRecordTableName");
    
    modelBuilder.Entity<Record>()
        .HasKey(r => r.Ndx);
    
    modelBuilder.Entity<Record>()
        .Property(r => r.Ndx)
        .HasColumnName("ndx"); // probably redundant because case doesn't matter
    
    modelBuilder.Entity<Record>()
        .Property(r => r.CreatedAt)
        .HasColumnName("created");
    
    modelBuilder.Entity<Patient>()
        .ToTable("YourPatientTableName");
    
    modelBuilder.Entity<Patient>()
        .Property(r => r.Ndx)   // Yes, no typo: It must be Ndx, NOT RecordNdx !
        .HasColumnName("record_ndx");
    
    modelBuilder.Entity<Patient>()
        .Ignore(r => r.RecordNdx);
    

    最后一个映射(忽略RecordNdx 属性)很重要。这意味着您的关键属性将是Patient.Ndx。我认为您不能将派生类中的任何属性设为关键属性。关键属性必须始终位于继承层次结构的基类中。但是,此属性可以映射两次(或通常在 TPT 继承链中的每个实体一次)映射到每个表的不同列名 - 从 EF 6 开始。

    完全摆脱RecordNdx 属性将是最干净的解决方案。但是,既然您说您不能触摸您的属性,那么将RecordNdx 的值与Ndx 属性耦合至少是有意义的(如果您可以更改属性获取器和设置器) :

    public virtual int RecordNdx
    {
        get { return Ndx; }
        set { Ndx = value; }
    }
    

    编辑

    我刚刚使用 EF 6.1 测试了上面的 Code-First 映射,它确实有效! Record 表中的主键列是 ndxPatient 表中的主键列是 record_ndx。在这些 EF 之间创建了 TPT 映射所需的一对一关系。

    编辑 2

    这是我用过的完整测试程序(当前 EF 6.1 Nuget 包,.NET 4.5,VS 2012,SQL Server 2012 Express):

    using System;
    using System.Data.Entity;
    
    namespace EFTPT6
    {
        public class Record
        {
            public virtual int Ndx { get; set; }
            public virtual DateTime CreatedAt { get; set; }
        }
    
        public class Patient : Record
        {
            public virtual int RecordNdx { get; set; }
            public virtual string Name { get; set; }
        }
    
        public class MyContext : DbContext
        {
            public DbSet<Record> Records { get; set; }
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Record>()
                    .ToTable("Records");
    
                modelBuilder.Entity<Record>()
                    .HasKey(r => r.Ndx);
    
                modelBuilder.Entity<Record>()
                    .Property(r => r.Ndx)
                    .HasColumnName("ndx");
    
                modelBuilder.Entity<Record>()
                    .Property(r => r.CreatedAt)
                    .HasColumnName("created");
    
                modelBuilder.Entity<Patient>()
                    .ToTable("Patients");
    
                modelBuilder.Entity<Patient>()
                    .Property(r => r.Ndx)
                    .HasColumnName("record_ndx");
    
                modelBuilder.Entity<Patient>()
                    .Ignore(p => p.RecordNdx);
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
                using (var ctx = new MyContext())
                {
                    ctx.Database.Initialize(true);
                    string sql = ctx.Records.ToString();
                }
            }
        }
    }
    

    程序末尾的字符串sql是:

    SELECT 
        CASE WHEN ( NOT (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL)))
            THEN '0X'
            ELSE '0X0X'
        END AS [C1], 
        [Extent1].[ndx] AS [ndx], 
        [Extent1].[created] AS [created], 
        CASE WHEN ( NOT (([Project1].[C1] = 1) AND ([Project1].[C1] IS NOT NULL)))
            THEN CAST(NULL AS varchar(1))
            ELSE [Project1].[Name]
        END AS [C2]
        FROM  [dbo].[Records] AS [Extent1]
        LEFT OUTER JOIN  (SELECT 
            [Extent2].[record_ndx] AS [record_ndx], 
            [Extent2].[Name] AS [Name], 
            cast(1 as bit) AS [C1]
            FROM [dbo].[Patients] AS [Extent2] ) AS [Project1]
                ON [Extent1].[ndx] = [Project1].[record_ndx]
    

    看起来映射受到尊重,即RecordsPatients 表由ndxrecord_ndx 列连接。

    编辑 3

    重要的是上下文类不包含派生实体的集合,即没有public DbSet&lt;Patient&gt; Patients { get; set; }。如果确实如此,则忽略映射 modelBuilder.Entity&lt;Patient&gt;().Property(r =&gt; r.Ndx).HasColumnName("record_ndx");,并且 EF 预计 Patient 中的主键名称是 ndx 而不是 record_ndx。例如上面 SQL 的最后一行变成了ON [Extent1].[ndx] = [Project1].[ndx]

    【讨论】:

    • 不幸的是,对我来说它实际上仍然不起作用。仍然使用使用“ndx”作为患者的 pk 列的 SQL 代码。模型构建器上注册命令的顺序重要吗?
    • 我还使用最新的 EF 测试版 6.1.1 对其进行了测试。你不会碰巧在 github 上有你的测试代码,是吗? :)
    • @AndreasH.:我已经发布了上面的测试程序和我得到的简单查询的 sql。您使用 SQL Server 还是其他数据库/提供程序?
    • 我会试试的。 SQL Server 2008R2,“System.Data.Entity.SqlServer.SqlProviderServices,EntityFramework.SqlServer”; providerName = "System.Data.SqlClient"。有趣的是,即使是最新的 EntityFramework.dll,VS 和 ILspy 也会报告程序集版本 6.0.0
    • @AndreasH.:我注意到您的上下文中显然设置了 DbSet&lt;Patient&gt; patients。如果我也将它添加到我的测试中,我会遇到和你一样的问题。因此,您似乎必须从您的上下文中删除此 patients 集。仍然可以使用db.Records.OfType&lt;Patient&gt;()...查询患者,这个集合的存在(即使没有使用)会导致错误的SQL,这有点奇怪。对我来说看起来像是一个 EF 错误。
    猜你喜欢
    • 1970-01-01
    • 2012-04-13
    • 1970-01-01
    • 1970-01-01
    • 2012-10-30
    • 1970-01-01
    • 1970-01-01
    • 2015-11-29
    • 1970-01-01
    相关资源
    最近更新 更多