Довольно часто различные сущности данных обладают сходными свойствами и обрабатываются одинаково. При обработке объектов в программе общность  выражается интерфейсами, а обработка производится обобщенными методами (generics).

При использовании Linq можно написать аналогично

/// <summary>
/// Интерфейс для всех сущностей,
/// которые могут не показываться позьзователю
/// </summary>
public interface IVisible
{
    bool Visible { get; set; }
}

/// <summary>
/// Выбор только видимых сущностей
/// </summary>
public static class IVisibleExtensions
{
    public static IQueryable<T> Visible<T>(this IQueryable<T> q) 
        where T : IVisible
    {
        return q.Where(o => o.Visible);
    }
}

Потом можно создать модель данных (я использую EF), сделать сущности с полем Visible и с помощью partial-класса «прицепить» интерфейс к сущности

/// <summary>
/// Какая-то сущность
/// </summary>
public partial class Entity1 : IVisible
{
}

Теперь тестовый код…

static void Main(string[] args)
{
    var context = new Model1Container();
    foreach (var item in context.Entity1Set.Visible())
    {
        Console.WriteLine(item);
    }
}

Этот код отваливается с ошибкой Unable to cast the type 'Entity1' to type 'IVisible'. LINQ to Entities only supports casting Entity Data Model primitive types.

Проблема заключается в том что выражения o.Visible внутри generic метода Visible<T> преобразуется в expression tree вида
((IVisible)o).Visible. Linq2EF (как и другие Linq-провайдеры) не понимают что делать с типом IVisible и генерация SQL-выражения падает.

Можно конечно собирать expression нужного вида руками для каждого обобщенного метода обработки запроса, но это не наш путь.

Лучше написать метод, который устраняет очевидно лишние приведения типов в expression. Для этого сделаем extension-метод Fix.

public static IQueryable<T> Fix<T>(this IQueryable<T> q)
{
    var visitor = new FixupVisitor();
    return q.Provider.CreateQuery<T>(visitor.Visit(q.Expression));
}

Теперь осталось написать FixupVisitor. В .NET 4 включен класс ExpressionVisitor в пространстве имен System.Linq.Expressions, который поддерживает в том числе расширенные деревья выражений. Для .NET 3.5 можно взять IQToolkit.

internal class FixupVisitor: ExpressionVisitor
{
    protected override Expression VisitUnary(UnaryExpression u)
    {
        if (u.NodeType != ExpressionType.Convert)
        {
            return base.VisitUnary(u);
        }

        var operandType = u.Operand.Type;
        var expressionType = u.Type;
        if (expressionType.IsInterface 
            && operandType.GetInterfaces()
                          .Contains(expressionType))
        {
            return base.Visit(u.Operand);
        }
        else
        {
            return base.VisitUnary(u);
        }
    }
}

Этот визитор просто выкидывает избыточное приведение типа к интерфейсу.

Еще один тестовый код

static void Main(string[] args)
{
    var context = new Model1Container();
    foreach (var item in context.Entity1Set.Visible().Fix())
    {
        Console.WriteLine(item);
    }
}

Добавился только вызов Fix в конце и все работает.

Продолжение следует…

Теги : Linq, .NET