Переписывание выражения Linq в контексте NHibernate + DDD

У меня есть сложный вопрос о преобразовании выражений Linq. У меня был хороший поиск, но я не смог найти ничего, что могло бы покрыть это дело. Я неплохо знаком с Linq, по крайней мере, с точки зрения создания и передачи лямбда-выражений в методы, но я несколько слабее в работе с Expression.

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

Эти проекты сопоставляются с помощью Fluent (не с автоматическим сопоставлением), поэтому я добавил метод в свой базовый класс карт в форме

HasManyQueryable<TCollection>(Expression<Func<T, IQueryable<TCollection>>> memberExpression, Expression<Func<T, TCollection, bool>> selector)

Этот метод извлекает соответствующий PropertyInfo из первого Expression (который указывает элемент для сопоставления). Селектор Expression содержит отношение между родительским и дочерним объектами в качестве замены обычного отображения NHibernate.

Итак, предположим, у меня есть селектор на карте для типа домена User (который выше T):

HasManyQueryable<Transaction>(x => x.Transactions, (u, t) => t.User == u);

Это задает сопоставление между подмножеством всех Transactions, где Transaction.User — это предоставленный User u, и свойством User.Transactions, которое равно IQueryable<Transaction>. Когда реальный объект User создан, мне нужно превратить его в

Expression<Func<Transaction, bool>> expression = (t => t.User == this)

где this — строящийся объект User. Другими словами, я хочу взять общее правило, которое говорит, как сопоставить Users с Transactions, и превратить его в правило о сопоставлении этого User с Transactions. Затем я могу использовать это выражение для создания IQueryable<Transaction> из репозитория, выполнив запрос Linq, таким образом:

return Repository.For<Transaction>().Where(selector);

Это может работать только тогда, когда селектор равен Func<Transaction, bool>, поэтому мне нужно превратить исходное выражение, которое сгенерирует Func<User, Transaction, bool> в Func<Transaction, bool>.

Это дает мне коллекцию IQueryable, в которой все операции запросов выполняются как запросы Linq-to-NHibernate, и, таким образом, вся коллекция никогда не загружается в память (да, я знаю, что вы можете сформулировать запрос, который действительно заставит это сделать, но Я могу поймать их во время проверки кода).

Фу. Надеюсь, это имеет смысл.

Кто-нибудь, у кого есть навыки переписывания Expression, может указать мне правильное направление?


person Neil Hewitt    schedule 24.08.2011    source источник
comment
Для тех, кого это волнует... оказывается, то, что я хочу сделать, невозможно. Не бит Expression/Func - это решается путем каррирования, как указывает принятый ответ. Вы просто не можете получить правильный тип запроса Linq из NHibernate так, как я хотел. В конце концов я выбрал менее аккуратное, но работающее решение.   -  person Neil Hewitt    schedule 26.08.2011


Ответы (1)


Честно говоря, я немного не понимаю, что вы пытаетесь сделать с точки зрения предметной области. Но с точки зрения кода все, что вам нужно, — это каррированная функция — метод, который принимает User и создает Func. Примером метода может быть такой, хотя вы можете сделать то же самое в строке:

Func<Transaction,bool> UserSpecificSelector(User user,
                                            Func<User,Transaction,bool> selector)
{
    return t => selector(user, t);
}

Итак, чтобы получить пользовательский селектор, вы должны сделать...

Func<User, Transaction, bool> selector = // whatever;
User user = //whatever;
Repository.For<Transaction>().Where(t => selector(user, t));

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

person BishopRook    schedule 24.08.2011
comment
Ага, я почти уверен, что ты попал в точку. Я нашел еще один ответ, предлагающий каррировать что-то другое, и внезапно сказал «бинг!». Перепишите код сейчас, чтобы посмотреть, пойдет ли он. - person Neil Hewitt; 25.08.2011
comment
С точки зрения предметной области я пытаюсь добиться того, чтобы коллекции NHibernate были фасадом над поставщиком NH Linq. Это довольно абстрактно, и я согласен, что не очень хорошо это описал... - person Neil Hewitt; 25.08.2011
comment
Что ж, в таком случае поздравляю, вы первый человек, о котором я слышал, для которого каррирование пошло на ура! вместо «какого черта??» Это определенно заняло у меня некоторое время, чтобы грок. - person BishopRook; 26.08.2011
comment
У меня никогда не было делегатов в .NET до лямбда-выражений эпохи 3.5. Потребовалось время, чтобы вникнуть, но теперь это вторая натура. Так что в этом случае я думаю об этом как о делегате, возвращающем делегата, что просто имеет смысл. К сожалению, то, что я пытался сделать с доменом, оказалось непрактичным, но теперь у меня есть новый трюк в арсенале. Спасибо! - person Neil Hewitt; 26.08.2011