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