Довольно часто различные сущности данных обладают сходными свойствами и обрабатываются одинаково. При обработке объектов в программе общность выражается интерфейсами, а обработка производится обобщенными методами (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 в конце и все работает.
Продолжение следует…