Разделение сущностей, когда ключевой столбец имеет разные имена? - PullRequest
12 голосов
/ 22 февраля 2012

Я использую Entity Framework 4.3.1 Code-First, и мне нужно разделить сущность между двумя таблицами.Таблицы имеют общий первичный ключ, и он равен 1: 1, но столбцы не имеют одинаковых имен в каждой таблице.

Я не контролирую макет данных и не могу запрашивать какие-либо изменения.

Так, например, таблицы SQL могут быть

SQL data tables

И это будет моя сущность ...

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, ища столбец «ThePrimaryKeyId» в таблице «ExtendedTable», которого не существует.

EDIT Чтобы уточнить, чтоЯ определил выше, может (и действительно) работать, если PK на «ExtendedTable» следовал соглашениям об именах.Но это не так, и я не могу изменить схему.

По сути, для генерации 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 */

Редактировать Я снова попробовал подход 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'"

Удаление свойства Id из класса ExtEntity приводит к ошибке, потому что тогда у сущности нет определенного ключа.

Ответы [ 8 ]

3 голосов
/ 22 апреля 2015

Я работал над этой самой проблемой в течение нескольких дней, и в итоге я установил имя столбца поля 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]
2 голосов
/ 25 июня 2014

Переместите имя 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");
    });
}
2 голосов
/ 31 марта 2012

Я не могу найти ничего, что конкретно указывает, что имя столбца должно быть одинаковым в обеих таблицах;но я также не могу найти ничего, что говорит, что это не так, или объясняет, как вы бы отобразили этот сценарий.Каждый пример, который я могу найти, содержит ключ с одинаковым именем в обеих таблицах.Мне кажется, что это дыра в дизайне DbContext.

1 голос
/ 24 мая 2013

Похоже, что это было исправлено в Entity Framework 6. См. Эту проблему http://entityframework.codeplex.com/workitem/388

1 голос
/ 02 апреля 2012

И просто предоставить (как я и обещал) отображение 1-к-1 (две сущности, две таблицы), для чего оно стоит.
Вот что работает для меня и должно в вашем случае ...

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 (которые я не мог изменить) в моей таблице пользователей, которая имеет собственный идентификатор пользователя, указывающий на внешнюю таблицу / таблицу aspnet и идентификатор.
Правда, вы не можете сделать это, используя один класс модели C # - носторона C # гораздо более гибкая, и если вы можете контролировать C #, который должен дать тот же эффект, по крайней мере, на мой взгляд (как и в тесте, вы можете получить к нему доступ всегда через расширенную сущность, как расширенную, так и основные столбцы, и онивсегда соответствуют 1 к 1 и остаются «синхронизированными».
Надеюсь, что это поможет некоторым
ПРИМЕЧАНИЕ: вам не нужно беспокоиться о идентификаторе fk и т. д. - просто всегда открывайте и добавляйте основную запись через MainEntryи id-ы будут в порядке.

РЕДАКТИРОВАТЬ:
Вы также можете сделать следующее, чтобы получить видимость иметь дело только с одним классом (то есть своего рода сплит)

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 вместо зависимых.
(также всессылки должны быть без «виртуальных», если вам требуется один-к-одному)
(и MainTable можно сделать «внутренним», так как он здесь, поэтому он не виден снаружи - его нельзя вкладывать как этот EFне позволяет - рассматривается как NotMapped)
... ну, это лучшее, что я мог сделать:)

1 голос
/ 28 марта 2012

Здесь нет Visual Studio, но попробуйте это с подходом 1: 1:

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

Обновление:
Вот некоторые ссылки, которые могут помочь (не удалось найти реальную ссылку)

Как объявить отношения один-к-одному, используя Entity Framework 4 Code First (POCO)
Entity Framework 4 CTP 4 Code First: как работать с нетрадиционными именами первичных и внешних ключей

0 голосов
/ 03 ноября 2016

Я столкнулся с этой проблемой и решил, добавив атрибут Column для совпадения имен обоих столбцов. [Key] [Column("Id")] public int GroupId { get; set; }

0 голосов
/ 03 апреля 2012

Я хотел бы предложить использовать некоторые аннотации данных, такие как:

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; }
}
...