Computed fields for any LINQ provider

    Hello, Habr!

    Today I want to talk about a small library that I recently wrote on my knee in just a few hours. This library can decompile methods into their λ representation.

    Why this might be needed - under the cut.

    Intro


    In life, it happens that in LINQ you need to use a computed field, for example, we have an Employee class with a computed field FullName

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

    And here the customer comes to you and says that we need to add a search by the full name of the employee. You take a short time to think and write the following query:

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

    Yes, with a field as simple as FullName , you can do this, but what if the field is not so simple? For example, a calculated field from one of the projects in which I participated.

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

    This is harder. So let's get started. What do we have to solve such problems?

    at NHibernate


    If you use NHibernate, you can map this field as a formula, but this path is not very refactoring friendly, and besides supports only sql, and if you are writing an application that you plan to use with different databases, then you need to be especially careful.

    Only supported in NHibernate.

    Microsoft.Linq.Translations


    To do this, we need to rewrite our class and query as follows:

    class Employee 
    {
        private static readonly CompiledExpression fullNameExpression
         = DefaultTranslationOf.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()
    

    Everything is fine, the request looks beautiful, but the declaration of the property is just awful. In addition, Evaluate compiles the λ-expression at the time of execution, which, in my opinion, is no less terrible than specifying a calculated field.

    And finally, we come to my creation - DelegateDecompiler

    DelegateDecompiler


    All that is needed is to mark the calculated fields with the [Computed] attribute , and convert the request using the .Decompile () method

    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()
    

    In my opinion it’s elegant (you won’t praise it yourself - no one will praise it)

    When you call .Decompile (), the decompiler will find all the properties and methods marked with the [Computed] attribute and expand them. Those. the request will be converted to the form from the original example:

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

    The library uses Mono.Reflection ( GitHub , NuGet ) from Jean-Baptiste Evain , the creator of Mono.Cecil, as a decompiler . Mono.Cecil itself is not used due to its bulkiness.

    PS: Naturally, the fact that inside the calculated field should be supported by your LINQ provider.
    PPS: This alpha version is very far from release - use at your own risk.

    References


    Source Code on GitHub
    Package in NuGet

    Also popular now: