Я ищу способ настроить свой контекст так, чтобы при заполнении определенного свойства определенного объекта он извлекал данные из другой таблицы. Что-то похожее на этот поддельный синтаксис (при условии, что свойства навигации уже установлены) и выдуманная схема:
modelBuilder.Entity<MyEntity>(entity => {
entity.Property(e => e.PropFromOtherTable)
// the magic I'm looking for
.PopulateFrom(e => e.Destination?.Hotel.Contact.Id ?? 0);
}
В идеале я хотел бы сделать это в построителе моделей, чтобы свойство заполнялось во всех местах, где используется этот объект, без необходимости явно запрашивать свойство в любом заданном запросе.
Я понимаю, что могу использовать Select, чтобы получить любую форму, которую захочу, но мой основной мотиватор здесь — избегать необходимости изменять сигнатуры более 40 методов и более 150 модульных тестов и не привлекать массу дополнительных данных (в конечном итоге я просто нужен идентификатор контакта, мне не нужны отели или контакты).
Может быть, создать представление и сопоставить его с этим представлением? См. также Сопоставление табличных функций
Да, я рассматривал представление, но это широко используемый класс, который придется обновить в других местах. Изменение всего, чтобы использовать представление для этого варианта использования (и во избежание дополнительной работы), кажется плохой идеей :), а создание нового представления/объекта приводит к той же проблеме «изменения подписи», которую я хочу избежать. Но спасибо, это хорошая идея!
В EF нет настоящей магии для этого. Если вам нужна магия, вы, возможно, можете положиться на триггер базы данных для заполнения поля при вставке, но имейте в виду, что вам нужно будет обновить объект из базы данных в EF, чтобы увидеть это значение. Однако я бы рекомендовал воздержаться от этого.
Я бы создал службу, которая сделает это за вас. Таким образом, вам будет проще написать тест. Я знаю, вы упомянули, что не хотите обновлять весь существующий код, но это возможность его почистить. Не усложняйте беспорядок.
Ха-ха-ха, да, это довольно грязный и запутанный код. Наверное, придется заняться уборкой, вопрос только в том, на чем остановиться :)
Краткий ответ: нет, за исключениями, использующими отношения разделения таблиц, вы можете извлекать значения сущностей из связанных таблиц. Используя модели представлений и проекции, такие как Automapper или Mapperly, это было бы тривиально, поскольку они завершают «работу» по построению выражения запроса Select
при чтении, чтобы EF погружался в модель предметной области и извлекал только те поля, которые вы хотите использовать.
Вы можете рассмотреть возможность обновления вашей схемы, включив в нее вычисляемый столбец для получения связанного ContactId, а затем вы можете сопоставить его с сущностью, которая будет обрабатываться как вычисленное значение. (Только для чтения) Вероятно, это будет самый простой способ перенести это свойство в вашу сущность. Это работает аналогично столбцу «Идентификация», просто настройте его как [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
. Если вы используете Code-First для управления своей схемой, вам нужно будет настроить выбор идентификатора клиента.
Другой вариант — несопоставленный вычисляемый столбец на стороне клиента, например:
public class MyEntity
{
// ...
[NotMapped]
public int ContactId => Destination?.Hotel.Contact.Id ?? 0;
}
Однако это будет надежно только в том случае, если любое применимое место назначения + отель + контакт будет загружено или предварительно заполнено DbContext или у вас включена отложенная загрузка. (что было бы довольно дорогим свойством для чтения, поскольку для получения этого свойства пришлось бы загружать весь пункт назначения, отель и контакт). Если к свойству обращаются нечасто и его не будут «затрагивать» в таких вещах, как итерации списка, это не является Совершенно ужасная ситуация для ленивой загрузки, но создает ситуацию с миной, которая может вызвать проблемы с производительностью.
«Хакерский» подход с использованием Automapper (или других поддерживаемых IQueryable
картографов, таких как Mapperly) заключается в проецировании на отдельные объекты, где вы не работаете с моделями представлений. Подобно несопоставленному вычисленному на стороне клиента значению, приведенному выше, мы используем несопоставленное свойство:
public class MyEntity
{
// ...
[NotMapped]
public int ContactId { get; protected set; }
}
Затем у нас есть конфигурация Automapper. Обычно я помещаю их как статические методы в свои модели представления, но мы можем добавить их к сущности в качестве примера:
public static MapperConfiguration BuildConfig()
{
var config = new MapperConfiguration(config =>
config.CreateMap<MyEntity, MyEntity>()
.ForMember(x => x.ContactId, opt => opt.MapFrom(src => src.Destination.Hotel.Contact.Id ?? 0));
); вернуть конфигурацию; }
Затем, читая наши сущности, вставьте ProjectTo<MyEntity>
перед ToList()
или FirstOrDefault()
и т. д., где эта сущность читается:
Т.е.
var myEntity = context.MyEntities
.Where(x => ...)
.ProjectTo<MyEntity>(MyEntity.BuildConfig())
.ToList();
Недостаток этого подхода заключается в том, что полученные объекты будут отсоединены, поэтому, если вы хотите выполнять обновления, вам нужно будет их присоединить. Это означает, что в первую очередь нужно проверять существующие отслеживаемые объекты. Сущности, считываемые без проекции, не будут иметь доступный ContactId. Вы не можете использовать свойство ContactId для изменения контакта, это необходимо сделать через Destination.Hotel.Contact. Обратите внимание, что изменение этой связи не приведет к автоматическому обновлению ContactId в MyEntity. Вы хотели бы использовать больше подхода DDD к объекту, например «MyEntity.ChangeDestination(Destination)», который обновил бы пункт назначения и повторно вычислил ContactId. Честно говоря, было бы лучше использовать ViewModels, чтобы избежать путаницы и хлопот, которые могут возникнуть из-за отдельных сущностей и несопоставленных свойств. Хотя это вариант, который вы можете изучить.
Это несколько хороших идей. Я не упомянул, что это сервер MS SQL, но это так, и я не думаю, что его вычисляемые столбцы могут читать из любой другой таблицы (или даже из любой другой строки), по крайней мере, не очень хорошо. Я определенно хотел избежать ситуации, когда каждый отдельный запрос должен явно выполнять дополнительный шаг для заполнения поля, потому что я не хочу, чтобы кто-то видел, что поле доступно, и задавался вопросом, почему оно не заполняется. Это очень интересные идеи, но я не думаю, что они подойдут для этой кодовой базы или команды. Ну что ж, я стисну зубы, поступлю правильно и подчищу код. Спасибо!
Короткий ответ: нет, EF не имеет свойств формулы, как NHibernate.