Entity Framework - Обновленные отношения не извлекают данные, созданные из старых отношений - PullRequest
0 голосов
/ 18 февраля 2020

Я недавно обновил отображение Entity Framework (версия 6.2.0), чтобы повысить производительность моего приложения, но без изменения схемы базы данных. По сути, у меня было отношение One-to-Many, которое я превратил в отношение * 1003. *

Когда я создаю новые объекты с этим новым отображением, все работает как положено: объекты сохраняются в базе данных с правильными значениями в столбцах внешнего ключа, et c. Когда я перезапускаю свое приложение (и сбрасываю DbContext) и получаю доступ к этим объектам, свойства навигации моих сущностей выполняются.

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


Конкретно, у меня есть две таблицы, определенные следующим образом:

 ______           _____________
|Sample|         |Sample_Result|
|------|         |-------------|
|Id    |<--------|Result_Of    |
|______|  Fk     |_____________|

С точки зрения бизнеса образец может иметь только один результат. Но раньше мы отображали его в Entity Framework как один-ко-многим, как показано ниже:

[Table("Sample")]
public class SampleEntity
{
    public virtual IList<SampleResultEntity> Results { get; set; }
}

[Table("Sample_Result")]
public class SampleResultEntity
{
    [Required]
    [Column("Result_Of")]
    public Guid ResultOfFk { get; set; }

    [ForeignKey(nameof(ResultOfFk))]
    public virtual SampleEntity ResultOf { get; set; }
}

У нас была ужасная производительность с этим отображением, поэтому я недавно обновил его до One-to-One. отношение:

[Table("Sample")]
public class SampleEntity
{
    public virtual SampleResultEntity Result { get; set; }
}

[Table("Sample_Result")]
public class SampleResultEntity
{
    [Required]
    [Column("Result_Of")]
    public Guid ResultOfFk { get; set; }

    public virtual SampleEntity ResultOf { get; set; }
}

И в свой dbContext я добавил следующее отображение:

 modelBuilder.Entity<SampleResultEntity>()
            .HasRequired(sr => sr.ResultOf)
            .WithRequiredDependent(s => s.Result);

Когда я получаю доступ к образцам, созданным после обновления отображения, определяется sample.Result.

Когда я получаю доступ к образцам, созданным до обновления сопоставления, sample.Result является нулевым.

Когда я запрашиваю базу данных с внутренним объединением двух таблиц, например:

SELECT *
  FROM Sample s
  INNER JOIN Sample_Result sr ON s.Id = sr.Result_Of;

Я не «теряю» ряды (если у меня есть 40 образцов, я получаю 40 строк). Итак, внешние ключи хорошо установлены.

Может кто-нибудь объяснить мне такое поведение?

1 Ответ

1 голос
/ 19 февраля 2020

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

A 1- Схема to-many будет выглядеть примерно так:

Sample
SampleID (PK)

SampleResult
SampleResultID (PK)
SampleID (FK)

Схема 1-к-1 будет выглядеть следующим образом: Sample SampleID (PK)

SampleResult
SampleID (PK + FK)

Итак, в 1- Схема to-many, содержащая от 1 до 1 строки, вы имели бы Sample ID = 1 с SampleResult с SampleResultID = 16 и SampleID = 1. Если бы вы просто изменили отображение на 1-к-1, EF ожидал, что PK будут включены обе таблицы равны. Поскольку SampleResult PK - это SampleResultId, получить правильную запись невозможно, так как она будет сравнивать Sample.SampleId / w SampleResult.SampleResultId (не SampleResult.SampleId)

Ваши новые тестовые записи будут работать, потому что вы вероятно, было бы найти:

Sample.SampleID = **220**

SampleResult.SampleResultID = **220**
SampleResult.SampleID = 220

Если бы вы использовали столбцы Identity (Int) для ключей, эта проблема могла бы проявиться несколько иначе, если бы вы либо получали пустые ссылки, либо получали ссылки на неверные результаты. Поскольку вы используете GUID, и каждый идентификатор будет относительно универсально уникальным, результатом будут пустые ссылки.

Я бы порекомендовал использовать Profiler в вашей тестовой базе данных для захвата SQL, используемого при загрузке Sample / Пара результатов для проверки правильности объединения столбцов.

Без изменения схемы, если SampleResult.SampleResultId настроен как Identity / Default, и , вы можете гарантировать, что не существует экземпляров 1-ко-многим (несколько результатов на один образец), вы должны быть в состоянии обновить отображение в EF, чтобы обработать SampleId в SampleResult в качестве ключа. Таким образом, с вашим новым отображением EF свяжет 2 вместе. Это будет означать, что ваши предыдущие «новые» тестовые записи больше не будут работать, потому что они бы объединяли Sample.SampleID с SampleResult.SampleResultID, но ваши исходные записи должны работать, и новые записи также должны работать. Ключ EF не обязательно должен соответствовать PK в таблице, если вы используете DB-First (без миграций) и схема удовлетворена. Если для столбца DB для SampleResultId по умолчанию установлено значение NewSequentialId() или NewId(), вам не нужно беспокоиться об этом. Если в коде установлен PK, вам может потребоваться отобразить свойство как обычный столбец и сделать примечание для использования SampleId. Это может «сломаться», если у вас есть какие-либо другие объекты, ссылающиеся на SampleResult на основе SampleResultId. EF будет рассматривать SampleId в качестве ключа при присоединении.

И последнее замечание: я немного настороженно отношусь к выводу, что отношение 1-ко-многим между Sample и SampleResult привело к проблемам с производительностью, которые быть решена путем повторного сопоставления с 1-к-1. Несмотря на то, что несколько вводит в заблуждение наличие отношения «один ко многим», которое должно содержать только одного ребенка, в конце концов EF будет использовать Inner Join в обоих случаях. Я сильно подозреваю, что в игре что-то еще. Ключи GUID могут создавать проблемы c по мере роста системы, если вы используете NewId() / Guid.New для новых строк. Это связано с тем, что относительная случайность 128-битного ключа в кластерном индексе может привести к значительной фрагментации в индексных таблицах. Лучше использовать NewSequentialId() или реализацию на основе кода NewSequentialId() для генерации уникальных, но сортируемых GUID, которые минимизируют этот эффект, и объединяют это с регулярным обслуживанием индекса для таблиц базы данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...