Pull to refresh

Вычисляемые поля для любого LINQ-провайдера

Reading time 3 min
Views 14K
Привет, Хабр!

Сегодня я хочу рассказать, о маленькой библиотеке, которую я написал недавно на коленке всего за несколько часов. Эта библиотека может декомпилировать методы в их λ-представление.

Зачем это может понадобиться — под катом.

Intro


В жизни случается, что в LINQ нужно использовать вычисляемое поле, к примеру у нас есть класс Employee с вычисляемым полем FullName

class Employee
{
    public string FullName
    {
        get { return FirstName + " " + LastName; }
    }

    public string LastName { get; set; }

    public string FirstName { get; set; }
}

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

var employees = (from employee in db.Employees
                 where (employee.FirstName + " " + employee.LastName) == "Test User"
                 select employee).ToList();

Да, с таким простым полем, как FullName так можно поступить, но что делать, если поле не такое простое? Вот к примеру, вычисляемое поле из одного из проектов, в котором я учавствовал.

public class WayPoint 
{
    // все остальное опущено в целях наглядности
    public virtual bool IsValid
    {
        get 
        {
            return (Account == null) ||
               (Role == null || Account.Role == Role) &&
               (StructuralUnit == null || Account.State.StructuralUnit == StructuralUnit);
        }
    }
}

С этим сложнее. Итак, приступим. Что же у нас есть для решения таких задач?

<formula> в NHibernate


Если вы используете NHibernate, то можете замапить данное поле как формулу, но этот путь не очень дружелюбен к рефакторингу, к тому же <formula> поддерживает только sql, и если вы пишете приложение, которое планируется использовать с разными базами данных, то здесь вам нужно быть особенно осторожными.

Поддреживается только в NHibernate.

Microsoft.Linq.Translations


Для этого необходимо переписать наш класс и запрос следующим образом:

class Employee 
{
    private static readonly CompiledExpression<Employee,string> fullNameExpression
     = DefaultTranslationOf<Employee>.Property(e => e.FullName).Is(e => e.FirstName + " " + e.LastName);

    public string FullName 
    {
        get { return fullNameExpression.Evaluate(this); }
    }

    public string LastName { get; set; }

    public string FirstName { get; set; }
}

var employees = (from employee in db.Employees
                 where employee.FullName == "Test User"
                 select employee).WithTranslations().ToList()

Все хорошо, запрос выглядит красиво, а вот объявление свойства — просто ужасно. К тому же Evaluate компилирует λ-выражение в момент исполнения, что, на мой взгляд не менее ужасно, чем задание вычисляемого поля.

И, наконец, мы подошли к моему творениею — DelegateDecompiler

DelegateDecompiler


Все что нужно, это вычисляемое поля пометить атрибутом [Computed], а запрос преобразовать с помощью метода .Decompile()

class Employee 
{
    [Computed]
    public string FullName 
    {
        get { return FirstName + " " + LastName; }
    }

    public string LastName { get; set; }

    public string FirstName { get; set; }
}

var employees = (from employee in db.Employees
                 where employee.FullName == "Test User"
                 select employee).Decompile().ToList()

По-моему изящно (сам не похвалишь — никто не похвалит)

При вызове .Decompile() декомпилятор найдет все свойства и методы, помеченные атрибутом [Computed] и развернет их. Т.е. запрос будет преобразован к виду, из первоначального примера:

var employees = (from employee in db.Employees
                 where (employee.FirstName + " " + employee.LastName) == "Test User"
                 select employee).ToList();

Библиотечка в качестве декомпилятора использует Mono.Reflection (GitHub, NuGet) от Jean-Baptiste Evain — создателя Mono.Cecil. Сама Mono.Cecil не используется из-за ее громоздкости.

PS: Естественно, то что внутри вычисляемого поля должно поддерживаться вашим LINQ-провайдером.
PPS: Это альфа-версия очень далекая от релиза — используйте на свой страх и риск.

Ссылки


Исходный код на GitHub
Пакет в NuGet
Tags:
Hubs:
+36
Comments 16
Comments Comments 16

Articles