Pull to refresh

Comments 16

Хорошая идея, и сделано элегантно. Но, конечно, проще было бы не использовать вычисляемые поля в качестве поисковых. Или уже тогда заносить их в базу данных.
Да, проще не использовать, но иногда возникает необходимость, очень редко, но возникает. Не всегда можно занести в базу, к примеру если это не поле, а метод, принимающий какой-нибдуь параметр (такое так же поддерживается). Вообще библиотеку начал делать с прицелом на KnockoutMVC, где мне не нравились лямбды для вычисляемых полей во ViewModel, которые, если хотите использовать на сервере нужно предварительно скомпилировать. Самый первый пример

public class HelloWorldModel
{
  public string FirstName { get; set; }
  public string LastName { get; set; }

  public Expression<Func<string>> FullName() 
  {
    return () => FirstName + " " + LastName;
  }
}
После применения пример будет аналогичен тому, что в статье (нужно немного подпилисть KnockoutMVC)
Извините, не могу сейчас попробовать ваш код на практике, поэтому спрошу так. Насколько сложно отлавливать ошибки с таким подходом? То есть если я навешу [Compiled] на заведомо не вычисляемое на уровне БД выражение, что будет тогда — исключение в рантайме или проглотит?

Ну и заодно, как производительность по сравнению с первым подходом?
Будет или нет исключение — зависит от провайдера. EF — с радостью плюется, если что-то не поддерживается.

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

Трансформация выражений и конвертация их в SQL запрос хорошо кушают процессор.

Однако, для простых запросов это становится заметно только при нагрузке. На нашем проекте ничто не использует процессорное время так, как EF. Когда делаем нагрузочное тестирование, он оказывается основным боттлнеком. Приходится многое переводить на хранимки.

Но если нагрузка не планируется, то могу предложить синтаксическое и архитектурное улучшение:
можно реализовать обертки для IQueryProvider и IQueryable, позволяющие удобно подсовывать свои собственные трансформеры. Тогда клиентский код будет выглядеть как обычно, безо всяких .Decompile() и т.д., а вы сможете дать широкий простор фантазии в трансформации LINQ выражений.

Например, у нас с помощью этого реализована система фильтров.

PS Обещали, что в .NET 4.5 добавят кеширование сгенерированных SQL запросов и не будут повторно их генерировать при каждом вызове. Так что рекомендую перейти на 4.5.

Может у вас именно от того, что вы используете лишнюю абстракцию в вашем продукте и есть эти самые «боттлнеки»? Почему здесь нет QueryableWrapper и QueryProviderWrapper — именно для того, чтобы не заставлять пользователя совершать лишних телодвижений, а с этими дополнительными сущностями ему придется где-то сделать обворачивание. Тут же — где нужно, там вызвал .Decompile()

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

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

За тем, что там обещали в EF не слежу, т.к. использую NHibernate (заодно улучшаю LINQ провайдер в нем), а там уже давно все закешировано.
Не, тормоза есть даже там, где обертки не используются. Дело в том, что нет других способов получить значение переменой из дерева выражений, кроме как скомпилировать выражение в делегат. Компиляция — медленно. И я подозреваю, что декомпиляция — тоже.
>И я подозреваю, что декомпиляция — тоже.

Декомпиляция очень быстрый процесс (конечно зависит от количества инструкций). В .NET есть волшебный метод MethodBody.GetILAsByteArray который возвращает как понятно из названия IL код в виде массива байт, а Mono.Reflection просто приводит этот код к более удобному объектному виду github.com/jbevain/mono.reflection/blob/master/Mono.Reflection/MethodBodyReader.cs весь код «декомпилятора» 224 строки.

Дальше DelegateDecompiler в цикле интерпретирует инструкции — строит дерево выражений.
Немного полазил по коду, заинтересовало, а что обозначает использование @ перед именем параметра?)
и кстати, класс Cache это же ConcurrentDictionary
>Cache это же ConcurrentDictionary

Да, но не совсем. В ConcurentDictionary фабрика может быть вызвана больше 1 раза, у меня — нет. В этом главное различие.

>что обозначает использование @ перед именем параметра?)

Это экранирующий символ для зарезервированных слов.
Судя по частоте, с которой и я и другие с этим сталкиваемся — пора намекать на точно такую же фичу на уровне компилятора
Было бы хорошо, если б в языке не было разницы между делегатом и его лямбда представлением. Т.е., еслиб компилятор сам умел разбирать что хочет программист в каждом конкретном случае — дерево выражений или функцию.
Есть мнение, что дополнительной логики в сущностях быть не должно (FullName стерпеть можно, но что-то более сложное, типа вашего IsValid — нет, оно должно быть на уровне Specification).
Sign up to leave a comment.

Articles