Исключительно сопоставление существующей базы данных с существующими POCO без конструктора или CodeFirst в Entity Framework 6

1

Один важный факт @slauma дал в комментариях. Так что посмотрите на ответ И комментарии!

Я пытаюсь использовать компоненты, которые на самом деле используются с NHibernate, с EF6. Проблема в том, что у меня есть некоторые наследования TPT с первичными ключами разных имен. База данных и классы POCO даны, и я не могу изменить ни один из них, так что и CodeFirst, и разработчик EF не могут быть и речи.

Есть ли способ просто сопоставить существующий Db с существующими классами POCO, например, с данными файлами сопоставления.hbm.xml в NHibernate?

ОБНОВИТЬ:

Фактическая проблема, с которой я столкнулся, - это, прежде всего, отображение 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
}

И, как уже говорилось, изменение имени свойства или столбца не является вариантом.

Обновление II:

Это мой регистрационный код:

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]

что неверно, поскольку он выбирает [ndx] из [patienten] (должен быть record_ndx), а также пытается присоединиться к [ndx]

Теги:
entity-framework
entity-framework-6
ef-code-first
poco

1 ответ

1
Лучший ответ

Замечание о закрытии этой рабочей статьи в 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 значение RecordNdx с свойством Ndx (если вы можете изменить свойство getter и setter):

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

редактировать

Я только что проверил отображение Code-First выше с EF 6.1, и он действительно работает! Столбец первичного ключа в таблице Record - ndx а в таблице Patient - 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]

Похоже, что отображение соблюдается, т. ndx Таблицы Records и Patients record_ndx столбцами ndx и record_ndx.

Редактировать 3

Важно, чтобы класс контекста не содержал набор для производного объекта, т. public DbSet<Patient> Patients { get; set; } Ни один public DbSet<Patient> Patients { get; set; } public DbSet<Patient> Patients { get; set; } public DbSet<Patient> Patients { get; set; }. Если это modelBuilder.Entity<Patient>().Property(r => r.Ndx).HasColumnName("record_ndx"); игнорируется, и EF ожидает, что имя первичного ключа в Patient - ndx а не record_ndx. Например, последняя строка в SQL выше становится ON [Extent1].[ndx] = [Project1].[ndx].

  • 0
    Для меня это на самом деле все еще не работает, к сожалению. Тем не менее используется SQL-код, использующий ndx в качестве столбца pk для пациентов. Важен ли порядок команд регистрации в ModelBuilder?
  • 0
    Я также протестировал его с последней бета-версией 6.1.1 EF. У вас нет кода для тестирования на github? :)
Показать ещё 5 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню