在LINQ to Entities(EF4.1)中返回具有类型约束的泛型的输入类型

时间:2011-11-17 22:13:36

标签: c# linq generics linq-to-entities

我有一个简单的扩展方法,用于按标签过滤LINQ IQueryable。我正在使用LINQ to Entities,接口为:

public interface ITaggable
{
    ICollection<Tag> Tags { get; } 
}

以下内容不起作用,返回IQueryable<ITaggable>而不是IQueryable<T>

public static IQueryable<T> WhereTagged<T>(this IQueryable<T> set, string tag) where T:ITaggable
    {
        return set.Where(s=>s.Tags.Any(t=>t.Name.ToLower() == tag.ToLower()));
    }

这会导致LINQ to Entities强制转换异常:

  

“无法将类型'ReleaseGateway.Models.Product'强制转换为类型   'ReleaseGateway.Models.ITaggable'。 LINQ to Entities仅支持   转换实体数据模型基元类型。“   (System.NotSupportedException)System.NotSupportedException是   捕获:“无法将类型'Project.Models.Product'强制转换为类型   'Project.Models.ITaggable'。 LINQ to Entities仅支持强制转换   实体数据模型基元类型。“

它没有这样的约束,但我必须在我的应用程序代码中显式声明类型T:

public static IQueryable<T> WhereTagged<T>(this IQueryable<ITaggable> set, string tag)
{
    return set.Where(s=>s.Tags.Any(t=>t.Name.ToLower() == tag.ToLower())).Cast<T>();
}

问题:为什么类型约束会转换返回类型?我可以重写这个以利用从扩展方法调用者推断类型吗?

4 个答案:

答案 0 :(得分:7)

我怀疑问题来自对s.Tags的调用。由于sProduct,但您正在调用ITaggable.Tags,因此生成的表达式看起来更像:

set.Where(s=>((ITaggable)s).Tags.Any(...))

这只会混淆实体框架。试试这个:

((IQueryable<ITaggable>)set)
    .Where(s=>s.Tags.Any(t=>t.Name.ToLower() == tag.ToLower()))
    .Cast<T>();

由于IQueryable是协变接口,因此将该集视为IQueryable<ITaggable>,这应该有效,因为您的第二个示例基本上完全相同。

答案 1 :(得分:2)

我一直在寻找相同的答案而不满意所提供答案的句法清洁度,我一直在寻找并发现这篇文章。

TL;博士; - 为您的约束添加类,它可以正常工作

LINQ to Entities only supports casting EDM primitive or enumeration types with IEntity interface

public static IQueryable<T> WhereTagged<T>(this IQueryable<T> set, string tag)
    where T: class, ITaggable

答案 2 :(得分:0)

您永远不会显示使用它的位置。我认为你已经在IQueryable<ITaggable>首先传递了一个方法。

概念证明https://ideone.com/W8c66

using System;
using System.Linq;
using System.Collections.Generic;

public class Program
{
    public interface ITaggable {}

    public struct TagStruct : ITaggable {}
    public class  TagObject : ITaggable {}

    public static IEnumerable<T> DoSomething<T>(IEnumerable<T> input) 
        where T: ITaggable
    {
        foreach (var i in input) yield return i;
    }

    public static void Main(string[] args)
    {
        var structs = new [] { new TagStruct() };
        var objects = new [] { new TagObject() };

        Console.WriteLine(DoSomething(structs).First().GetType());
        Console.WriteLine(DoSomething(objects).First().GetType());               
    }
}

输出

Program+TagStruct
Program+TagObject

因此,它返回输入类型,而不是约束接口。

毫不奇怪,如果DoSometing需要两个接口,结果会是什么?

    public static IEnumerable<T> DoSomething<T>(IEnumerable<T> input) 
        where T: ITaggable, ISerializable

...

答案 3 :(得分:0)

dlev提到的最后你不需要那个Cast。

我假设产品类实现了ITaggable?我认为删除Cast会解决问题。