#c# #entity-framework #blazor #blazor-server-side
#c# #структура организации #блейзор #blazor-серверная часть
Вопрос:
Я работаю в C#/Blazor
У меня есть объект, скажем a Project
, который я извлекаю из базы данных, которая поставляется с внешними ключами и связанными с ними навигационными свойствами. Я извлекаю объект, а затем использую его в отключенном состоянии.
Как только объект извлечен, он загружается в форму для отображения/редактирования/обновления по мере необходимости. Я хочу создать отдельный клон Project
для использования в форме в качестве DTO, чтобы любые изменения можно было отбросить без проблем со ссылкой на извлеченный оригинал Project
.
Например, это упрощенный Project
класс:
public partial class Project { [Key] public int Id { get; set; } [Required] [StringLength(150)] public string ProjectName { get; set; } [Column("UpdatedBy_Fk")] public int UpdatedByFk { get; set; } [ForeignKey(nameof(UpdatedByFk))] [InverseProperty(nameof(UserData.ProjectUpdatedByFkNavigations))] public virtual UserData UpdatedByFkNavigation { get; set; } }
В форме я отобразил полное имя последнего человека, который обновил Project
с помощью @project.UpdatedByFkNavigation.FullName
. Пользователь вообще не может изменять поле навигации, оно только отображается.
Мой вопрос касается копирования элементов навигации. Для простоты теперь, внутри формы OnInitialized
, я передаю форме исходный project
объект и создаю новый objProject
, используя конструктор, подобный этому:
Project objProject = new() { Id = project.Id, ProjectName = project.ProjectName, UpdatedByFk = project.UpdatedByFk, UpdatedByFkNavigation = project.UpdatedByFkNavigation,
Это, по-видимому, работает и для создания отдельного Project
объекта, который не является ссылками и который я могу использовать в качестве своего DTO, однако я не уверен, уместно ли назначать virtual
свойства таким образом.
Соответствует ли этот подход рекомендациям по созданию копии объекта без ссылок с виртуальными полями навигации или я должен подойти к этому по-другому?
Ответ №1:
Это зависит от отношений. Ссылки важны в EF, поэтому вам необходимо решить, хотите ли вы, чтобы новый клон ссылался на те же данные пользователя или на новые и отличные данные пользователя с теми же данными. Обычно в отношениях «Многие к одному» требуется использовать одну и ту же ссылку или обновить ссылку для соответствия. Если оригинал был изменен идентификатором «Джон Смит» № 201, клон будет изменен идентификатором «Джон Смит» № 201 или изменен на идентификатор текущего пользователя «Джейн Доу» № 405, который будет такой же ссылкой «Джейн Доу», как и любая другая запись, измененная пользователем. Вы, вероятно, не захотите, чтобы EF создавал нового «Неизвестного Джона», который в конечном итоге получил бы идентификатор #545, потому что EF получил совершенно новую ссылку на данные пользователя, в которых есть копия «Неизвестного Джона».
Поэтому в вашем случае я бы предположил, что вы захотите сослаться на один и тот же существующий экземпляр пользователя, поэтому ваш подход верен. Где вам нужно быть осторожным, так это при использовании ярлыка, такого как Сериализация/десериализация, для создания клонов. В этом случае сериализация проекта и любая загруженная ссылка UpdatedBy создадут новый экземпляр данных пользователя с теми же полями и даже значением PK. Однако, когда вы перейдете к сохранению этого нового проекта с его новой ссылкой на данные пользователя, вы либо получите повторяющееся исключение PK, исключение «Объект с тем же ключом, который уже отслежен», либо получите новую запись «Неизвестный» с идентификатором #545, если эта сущность настроена на ожидание столбца идентификатора для своего PK.
Относительно типичных рекомендаций по использованию навигационных свойств по сравнению Поля FK: Мой совет-использовать одно или другое, а не оба. Причина этого в том, что при использовании обоих у вас есть два источника истины для отношений, и в зависимости от состояния сущности, когда вы меняете один, другой не обязательно отражает изменение автоматически. Например , в каком-то коде я смотрю на отношения, перейдя: project.UpdatedByFk
, в то время как другой код может использоваться project.UpdatedByFkNavigation.Id
. Ваше соглашение об именовании немного странное, когда дело доходит до свойства навигации. Для вашего примера я бы ожидал:
public virtual UserData UpdatedBy { get; set; }
В общем, я бы использовал исключительно свойство навигации и полагался на свойство тени в EF для FK. Это выглядело бы так:
public partial class Project { [Key] public int Id { get; set; } [Required] [StringLength(150)] public string ProjectName { get; set; } [ForeignKey("UpdatedBy_Fk")] // EF Core.. For EF6 this needs to be done via configuration using .Map(MapKey()). public virtual UserData UpdatedBy { get; set; } }
Здесь мы определяем свойство навигации, и, назначив имя столбца FK, EF создаст поле за кулисами для этого FK, которое недоступно напрямую. Наш код раскрывает один источник истины для отношений.
В некоторых случаях, когда важна скорость и мне практически не нужны соответствующие данные, я объявлю свойство FK и свойство навигации отсутствует.
В связи с этим:
[InverseProperty(nameof(UserData.ProjectUpdatedByFkNavigations))]
Я бы также рекомендовал избегать двунаправленных ссылок, если они не являются абсолютно необходимыми по той же причине. Если я хочу, чтобы все проекты были в последний раз изменены данным пользователем, я на самом деле ничего не выиграю от:
var projects = context.Users .Where(x =gt; x.Id == userId) .SelectMany(x =gt; x.UpdatedProjects) .ToList();
Я бы просто использовал:
var projects = context.Projects .Where(x =gt; x.UpdatedBy.Id == userId) .ToList();
В общем, вам следует постараться организовать свой домен и отношения в нем по совокупным корням: по сути, сущности, которые имеют важное значение для приложения на высшем уровне. Двунаправленные ссылки имеют схожие проблемы с наличием двух источников истины, которые не обязательно совпадают в данный момент времени при изменении этих отношений с одной стороны. Это во многом зависит от того, все ли отношения загружены или нет.
Если обе сущности являются совокупными корнями и взаимосвязь достаточно важна, то это может обеспечить двунаправленную ссылку и дополнительное внимание, которого она заслуживает. Хорошим примером этого могут быть отношения «многие ко многим», такие как отношения между классом (т. Е. Математическим классом A) и студентами, где в классе много студентов, в то время как у Студента много курсов, и с точки зрения класса имеет смысл перечислять своих студентов, а с точки зрения студента перечислять свои курсы.
Комментарии:
1. Вау, Стив, это удивительно полезно. Спасибо. Я действительно хочу сослаться на тот же экземпляр UserData для этого обновления, и я пытаюсь обновить только
Project
не все связанные объекты навигации. Объекты навигации используются более или менее для отображения, но я понимаю, что вы говорите о возможном создании будущих ошибок при сохранении. Я собираюсь перечитать этот ответ несколько раз и убедиться, что я правильно его применяю. Здесь есть о чем подумать.2. Для ясности того, как были созданы свойства и поля навигации, мы используем подход, основанный на базе базы данных, с помощью основных инструментов EF для обратного проектирования классов и DbContext. Я создал таблицу Бд
Project
, которая включаетCreatedByFk
ссылки на ограничения и ограниченияUserData
. Реверсивный инженер выплевывает обратное свойство, и все это автоматически основано на ограничении. Мне нужно переоценить это и убедиться, что это не делает что-то неподобающее.3. Ах, это объяснило бы название. Возможно, есть способы настроить этот инструмент для ожидания и обработки соглашения об именовании. Т. Е. он может ожидать чего-то вроде «CreatedById» и лучше справился бы с этим именем, но его можно настроить для обработки «*Fk». Честно говоря, я никогда не был поклонником использования дизайнеров/инструментов для отображения EF. У самого EF есть некоторые странности с соглашениями, когда дело доходит до ожиданий имен FK. В худшем случае вы могли бы использовать его, чтобы помочь настроить начальные сопоставления, исправить странные, которые он предлагает, а затем взять на себя управление отображением вручную.
4. Кроме того, инструменты, как правило, хотят отображать отношения, не зная о том, какие из них на самом деле достаточно важны для полезных двунаправленных ссылок, поэтому они отображают все . Еще одна причина использовать их в качестве первого прохода, а затем удалить все, что не является необходимым. Ссылки ничего не нарушат, но они могут добавить «шум» при работе с доменом со свойствами, которые никогда не используются, или путаницу, когда существует несколько способов учета получения данных, которые могут привести к неожиданной ленивой загрузке.
5. Определенно, существуют параметры конфигурации для соглашений об именах и других вещей, которые вы можете настроить, но я не слишком углублялся в них. Я довольно новичок в EF, поэтому я просто предполагаю, что инструмент знает больше, чем я, и оставляю его работать по стандартным настройкам по умолчанию, пока я не смогу более быстро с ним справиться. Вероятно, это не лучшая долгосрочная стратегия 🙂 Я еще раз взгляну на двунаправленные ссылки.