[C#] Связи Entity Framework с несколькими (отдельными) ключами в представлении


Answers

Редактировать Я не уверен, что это действительно возможно, и вот почему:

Предполагается, что внешний ключ ссылается на первичный ключ. У вас есть два поля, которые действуют как первичные ключи vwContact, но в зависимости от того, какой объект вы запрашиваете, это другое поле, которое является первичным ключом. У вас может быть только один первичный ключ одновременно, и хотя у вас может быть составной первичный ключ, вы не можете делать первичные ключевые вещи только с половиной - у вас должен быть сложный внешний ключ, с которым можно ссылаться.

Вот почему Entity Framework не имеет возможности указать столбец сопоставления на целевой стороне, поскольку он должен использовать первичный ключ.

Теперь вы можете наложить еще несколько объектов поверх объектов EF, чтобы выполнить ручной поиск и имитировать свойства навигации, но я не думаю, что вы действительно можете заставить EF делать то, что вы хотите, потому что сам SQL не будет делать то, что вы want - правило - это один первичный ключ для таблицы, и он не подлежит обсуждению.

Из того, что вы сказали о структуре вашей базы данных, может быть возможно написать сценарий миграции, который может дать контактным объектам согласованный первичный ключ и обновить все остальное, чтобы ссылаться на них с помощью этого единственного первичного ключа, а не на две системы, устаревшие данные, так как вы, конечно, можете присоединиться к любым полям, которые вам нравятся. Я не думаю, что вы получите бесшовно функциональную EF-модель без изменения вашей базы данных.

Оригинальный ответ, который не будет работать

Итак, vwContact содержит ключ KeyField который ссылаются многие SomeObject s и еще один ключ LegacyKeyField который ссылаются многие LegacyObject s.

Я думаю, именно так вы должны подходить к этому:

Дайте vwContact навигации SomeObject LegacyObject коллекций SomeObject и LegacyObject :

public virtual ICollection<SomeObject> SomeObjects { get; set; }
public virtual ICollection<LegacyObject> LegacyObjects { get; set; }

Дайте этим свойствам навигации внешние ключи:

modelBuilder.Entity<vwContact>()
    .HasMany(c => c.SomeObjects)
    .WithRequired(s => s.Contact)
    .HasForeignKey(c => c.KeyField);
modelBuilder.Entity<vwContact>()
    .HasMany(c => c.LegacyObjects)
    .WithRequired(l => l.Contact)
    .HasForeignKey(c => c.LegacyKeyField);

Беда в том, что я бы догадался, что вы уже пробовали это, и это не сработало, и в этом случае я не могу предложить вам многое другое, поскольку я не делал такого огромного количества вещей (наша база данных намного ближе к тем вещам, которые EF ожидает, мы должны были сделать относительно минимальные переопределения отображения, как правило, с отношениями «многие ко многим»).

Что касается ваших двух вызовов HasKey на vwContact , они не могут быть окончательным ключом для объекта, поэтому это либо составной ключ, который имеет оба из них, либо выбирает один, либо есть другое поле, которое вы не упомянули, реальный первичный ключ. Отсюда невозможно сказать, какой именно вариант есть.

Question

У меня возникают проблемы с настройкой модели Entity Framework 4.

Объект Contact отображается в базе данных как обновляемое представление. Кроме того, из-за истории базы данных этот вид контакта имеет два разных ключа: один из старой системы. Поэтому некоторые другие таблицы ссылаются на контакт с «ContactID», в то время как другие старые таблицы ссылаются на него с «LegacyContactID».

Поскольку это представление, в базе данных нет внешних ключей, и я пытаюсь вручную добавлять ассоциации в конструктор. Но свободные ассоциации, похоже, не дают способа указать, к какой области относятся.

Как мне построить эту модель?

public class vwContact
{
  public int KeyField { get; set; }
  public string LegacyKeyField { get; set; }
}

public class SomeObject
{
  public virtual vwContact Contact { get; set; }
  public int ContactId { get; set; } //references vwContact.KeyField
}

public class LegacyObject
{
  public virtual vwContact Contact { get; set; }
  public string ContactId { get; set; } //references vwContact.LegacyKeyField
}

ModelCreatingFunction(modelBuilder)
{
  // can't set both of these, right?
  modelBuilder.Entity<vwContact>().HasKey(x => x.KeyField);
  modelBuilder.Entity<vwContact>().HasKey(x => x.LegacyKeyField);

  modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).??? 
  //is there some way to say which key field this reference is referencing?
}



Я пробовал все, что вы можете себе представить, и обнаружил, что большинство решений не будет работать в этой версии EF ... возможно, в будущих версиях он поддерживает ссылку на другой объект с помощью уникального поля, но сейчас это не так. Я также нашел два решения, которые работают, но они скорее являются обходным решением, чем решениями.

Я пробовал все следующие вещи, которые не работали:

  • Отображение двух объектов в одну и ту же таблицу: это не разрешено в EF4.
  • Наследование с базы, которая не имеет ключевых определений: все корневые классы должны иметь ключи, так что унаследованные классы делятся этим общим ключом ... так как наследование работает в EF4.
  • Наследуя от базового класса, который определяет все поля, включая ключи, а затем использует modelBuilder, чтобы указать, какие базовые свойства являются ключами производных типов: это не работает, потому что methos HasKey, Свойство и другие, которые принимают члены в качестве параметров, должны ссылочные члены самого класса ... ссылки на свойства базового класса недопустимы. Этого не может быть сделано: modelBuilder.HasKey<MyClass>(x => x.BaseKeyField)

Две вещи, которые я делал, сработали:

  • Без изменений в базе данных: Карта в таблицу, которая является источником рассматриваемого представления ... то есть, если vwContact представляет собой представление для таблицы « Contacts , вы можете сопоставить класс с « Contacts и использовать его, установив ключ на KeyField , и другое сопоставление классов в представлении vwContacts с ключом LegacyKeyField . В классе Контакты, LegacyKeyField должен существовать, и вам придется управлять этим вручную при использовании класса «Контакты». Кроме того, при использовании класса vwContacts вам придется вручную управлять KeyField, если это не поле автоинкремента в БД, в этом случае вы должны удалить свойство из класса vwContacts.

  • Изменение БД: создайте другое представление, как и vwContacts , скажем vwContactsLegacy и сопоставьте его с классом, в котором ключ является LegacyKeyField , и сопоставьте vwContacts с исходным представлением, используя KeyField в качестве ключа. Все ограничения в первом случае также применяются: vwContacts должен иметь LegacyKeyField, управляемый вручную. И vwContactsLegacy, должен иметь KetField, если он не является автоинкрементным idenitity, иначе он не должен быть определен.

Существуют некоторые ограничения:

Как я уже сказал, эти решения являются обходными ... не реальными решениями, есть некоторые серьезные последствия, которые могут даже сделать их нежелательными:

  • EF не знает, что вы сопоставляете два класса с одним и тем же. Поэтому, когда вы обновляете одно, другое может быть изменено или нет, это зависит от того, кешируются ли объекты или нет. Кроме того, вы могли бы одновременно иметь два объекта, которые представляют одно и то же в хранилище резервных копий, поэтому скажите, что вы загружаете vwContact, а также vwContactLegacy, меняете оба, а затем пытаетесь сохранить оба ... вам нужно будет заботиться об этом самостоятельно.

  • Вам нужно будет управлять одним из ключей вручную. Если вы используете класс vwContacts, KeyFieldLegacy существует, и вы должны его заполнить. Если вы хотите создать vwContacts и ассоциировать с LegacyObject , вам нужно создать ссылку вручную, потому что LegacyObject принимает vwContactsLegacy, а не vwContacts ... вам нужно будет создать ссылку, установив поле ContactId .

Надеюсь, что это скорее помощь, чем разочарование , EF - мощная игрушка, но она далека от совершенства ... хотя я думаю, что в следующих версиях она будет намного лучше.




Иногда имеет смысл отображать его с другого конца отношений, в вашем случае:

modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).WithMany().HasForeignKey(u => u.LegacyKeyField);

однако это потребует, чтобы u.LegacyKeyField помечен как первичный ключ.

И тогда я дам свои два цента:

если Legacy db использует LegacyKeyField, возможно, устаревший db будет доступен только для чтения. В этом случае мы можем создать два отдельных контекста Legacy и Non-legacy и сопоставить их соответственно. Это потенциально может стать немного беспорядочным, поскольку вам нужно будет помнить, какой из объектов происходит из какого контекста. Но опять же, ничто не мешает вам добавить один и тот же первый объект кода EF в 2 разных контекста

Другим решением является использование представлений с ContactId для всех других устаревших таблиц и их отображение в один контекст. Это будет налоговая производительность ради более чистых объектов контекста, но это может быть встречено на стороне SQL: индексированные представления, материализованные представления, сохраненные процедуры и т. Д. Таким образом, LEGACY_OBJECT становится объектом VW_LEGACY с сообщением CONTACT.ContactId, затем:

modelBuilder.Entity<LegacyObject>().ToTable("VW_LEGACY_OBJECT"); 
modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).WithMany().HasForeignKey(u => u.ContactId);

Я лично поделился бы созданием «представлений картера» с CustomerId по старым таблицам, так как он стал более чистым с точки зрения c # слоя, и вы можете сделать эти представления похожими на реальные таблицы. Также сложно предложить решение, не зная, в чем именно заключается сценарий, с которым вы сталкиваетесь: запрос, загрузка, сохранение и т. Д.