无法隐式转换类型“System.Linq.IQueryable”为“System.Data.Entity.DbSet”

35

我刚接触 Linq,遇到了下面的情况:

编译时出现以下错误:无法隐式转换类型“System.Linq.IQueryable”为“System.Data.Entity.DbSet”。

var query = _db.Products;
if (bool) {
  query = query.Where(p => p.Id == id);
}

于是我尝试将var更改为IQueryable,然后它就可以工作了。

IQueryable<Product> query = _db.Products;
if (bool) {
  query = query.Where(p => p.Id == id);
}

但是后来,我又尝试再次修改它(见下文),结果成功了。

var query = from product in products
            select product;
if (bool) {
  query = query.Where(p => p.Id == id);
}

我只想知道为什么一个能够工作,而另一个却不能。

附上一个例子会很有帮助。谢谢!


你可能忘记了 using System.Data.Entity; - Eli Arbel
即使我有那个命名空间,仍然出现错误。但是我上面的代码可以工作。只是想知道为什么。 - Boy Pasmo
刚遇到了相同的问题,并通过等效于 var query = db.Products.Select(x => x); 解决了它。是的,这对我来说也很奇怪。 - Dimitar Nikovski
对我来说,query.AsQueryable().Where(......) 运行良好。 - Rohith K P
4个回答

49
第一种情况不起作用的原因是System.Linq.IQueryable是一个接口,它被实现在System.Data.Entity.DbSet类中。在C#中,如果类C实现了接口I,在类型之间的转换时,你可以把I当作C的基类(即使语义上class C : I也暗示了这种方法)。由于你不能将类(或接口)隐式地(即不详细说明)转换为它的子类之一,所以当你尝试这样做时会得到一个编译时错误。你可以反过来做,也就是将一个子类隐式转换为它的基类(或接口)。这正是第二种情况发生的情况。
在你的情况下,你可以通过显式转换来欺骗编译器:
query = (DbSet<Customer>) query.Where(p => p.Id == id);

我强烈建议你不要这样做,因为你最终会遇到混乱的异常。原因是query.Where(p => p.Id == id)的结果实际上并不是DbSet<Customer>的实例,而是一个表示在DbSet<Customer>上执行查询的结果的类,该类实现了IQueryable接口。
所以,总结一下,让我们回顾一下所有的情况:
情况1:
//query is of type DbSet<Customer>
var query = _db.Products; 
if (bool) {
  //here you're trying to assign a value of type IQueryable<Customer>
  //to a variable of it's descendant type DbSet<Customer>
  //hence the compile-time error
  query = query.Where(p => p.Id == id); 
}

场景2:

//here you implicitly cast value of type DbSet<Customer>
//to IQueryable<Customer>, which is OK
IQueryable<Customer> query = _db.Products; 
if (bool) {
  //here you're assigning a value of type IQueryable<Customer>
  //to a variable of the same type, which is also OK
  query = query.Where(p => p.Id == id); 
}

场景三:

//I assume you have the following line in your code
var products = _db.Products;
//query is of type IQueryable<Customer>, because you perform
//a query on the DbSet<Product>
var query = from product in products
            select product;
if (bool) {
  //here you're assigning a value of type IQueryable<Customer>
  //to a variable of the same type, which is OK
  query = query.Where(p => p.Id == id); 
}

编辑

我回答这个问题已经有一段时间了,尽管它的价值依然存在,但我倾向于使用稍微不同的方法(可能在原始答案时还没有可用,我不确定)。

将实现IQueryable<T>接口的对象转换为IQueryable<T>最简单(也是我认为最安全)的方法是:

var query = _db.Products.AsQueryable();

这仅仅返回对其 IQueryable<T> 接口实现的调用主题。执行查询时不应产生任何额外开销。现在,有一些评论建议使用一些技巧,我认为使用这些技巧可能是一个坏主意。
其中一个例子就是使用这个:
var queryable = query.Select(x => x);

虽然在查询对象时几乎完全没有副作用,但在处理某些 IQueryable<T> 实现时可能会造成一些危害。例如,当查询被转换为 SQL 查询时,它很可能会向执行的查询中添加一个冗余的 "SELECT * FROM ..."。这是最好的情况 - 在最有可能的情况下,它将添加更加繁琐的内容,例如 "SELECT x.P1, x.P2, ... FROM ... AS x"。当然,你可能觉得这没问题,但你应该意识到,根据实现的不同,这样的调用可能并不是“免费”的,尽管看起来什么都没做。

另一个例子:

query.Where(x => true)

这可能会在您的SQL查询中添加一个WHERE 1=1


IQueryable<MediaRef> medRef = context.MediaRefs; if(MediaId!=null) medRef=medRef.Where(w=>w.MediaId==MediaId);..但是这在我的情况下不起作用..(场景2) - Dragon
@Sadaquat 您所说的“不起作用”具体指什么?代码是否无法编译,如果是,出现了什么错误消息?否则,是否会抛出异常,如果是,异常消息是什么? - Grx70
1
它给出了编译时错误,同样的错误。尽管我已经使用你的第三种情况方法完成了我的工作,但我很好奇为什么第二种情况会产生编译错误:无法隐式转换类型“System.Linq.IQueryable<>”到“System.Data.Entity.DbSet<>”。 - Dragon

10
使用var关键字时,编译器会从赋值语句右侧的表达式推断出类型。例如:
var query = _db.Products;

query的类型为DbSet<Product>,无法赋值为Where扩展方法返回的任何IQueryable<Product>

当您使用查询语法时,query再次是IQueryable<Product>,这使它能正常工作。这相当于编写

var query = products.Select(t => t);
Select扩展方法类似于Where,返回的类型是IQueryable<Product>

1
products.Select(t => t) 是个很酷的技巧,省了我很多打字的时间。 - Muflix

0
var query = _db.Products.Where(x => true);
if (bool) {
  query = query.Where(p => p.Id == id);
}

0

这是因为_db.Products不是一个查询,而是一个DbSet。

第二个块可以工作是因为你将它转换为IQueryable,最后一个可以工作是因为它是一个实际的查询。


那么如果我声明一个 DbSet 并将其分配给一个泛型,DbSet 会被强制转换吗? - Boy Pasmo
1
在这种情况下,使用DbSet的理由非常少,只需查询数据库即可。 - dursk
DbSet<TEntity> 实现了 IQueryable<TEntity> 接口,因此它是一个查询。问题不在于 'query.Where(...)',而在于赋值语句 "query = query.Where(...)"。换句话说,问题不在于 DbSet,它不是一个查询,而在于 Query,它不是一个 DbSet。 - Denis Itskovich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接