IQueryable 在调用 Count c# 时返回 null
我在尝试从以下查询中获取计数时遇到问题:
var usersView = PopulateUsersView(); //usersView is an IQueryable object
var foo = usersView.Where(fields => fields.ConferenceRole.ToLower().Contains("role"));
其中 UsersView 是一个从名为 users 的 EF 实体填充的类(请参阅上面代码中的第一行)
这是 UsersView 类的类定义:
public class UsersView
{
public int UserId { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string PostCode { get; set; }
public string CountryName { get; set; }
public string WorkPlaceName { get; set; }
public string Gender { get; set; }
public string EMail { get; set; }
public string Company { get; set; }
public string RoleName { get; set; }
public string ConferenceRole { get; set; }
}
正如我所说,尝试执行 foo.Count() 行会返回 Null 异常,这可能是因为 ConferenceRole 列允许数据库中出现 Null。
现在我无法理解的是,当我直接在 ObjectQuery 上调用相同的查询时,会毫无例外地返回记录计数(即调用 foo2.Count())。
var foo2 = entities.users.Where(fields => fields.ConferenceRole.ToLower().Contains("role"));
是否可以进行与上面相同的查询,但使用 IQueryable usersView 对象?
(对我来说,使用 usersView 对象而不是直接查询Entity.users 实体至关重要)
编辑
下面是 PopulateUsersView 方法的代码
private IQueryable<UsersView> PopulateUsersView()
{
using (EBCPRegEntities entities = new EBCPRegEntities())
{
var users = entities.users.ToList();
List<UsersView> userViews = new List<UsersView>();
foreach (user u in users)
{
userViews.Add(new UsersView()
{
UserId = u.UserId,
Title = u.Title,
Name = u.Name,
Surname = u.Surname,
Street1 = u.Street1,
Street2 = u.Street2,
City = u.City,
PostCode = u.Post_Code,
CountryName = u.country.Name,
WorkPlaceName = u.workplace.Name,
Gender = u.Gender,
EMail = u.E_Mail,
Company = u.Company,
RoleName = u.roles.FirstOrDefault().Name,
ConferenceRole = u.ConferenceRole
});
}
return userViews.AsQueryable();
}
}
谢谢
更新...
谢谢大家,我终于找到了 IQueryable 和 IQueryable 之间差异的一个很好的答案ObjectQuery 对象。
作为解决方案,我检查 ConferenceRole 是否为空,然后检查 contains 方法,正如你们许多人所说的那样。
I have a problem trying to get the count out of the following query:
var usersView = PopulateUsersView(); //usersView is an IQueryable object
var foo = usersView.Where(fields => fields.ConferenceRole.ToLower().Contains("role"));
Where UsersView is a class which is populated from an EF entity called users (refer to the first line in the code above)
This is the class definition for the UsersView class:
public class UsersView
{
public int UserId { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string PostCode { get; set; }
public string CountryName { get; set; }
public string WorkPlaceName { get; set; }
public string Gender { get; set; }
public string EMail { get; set; }
public string Company { get; set; }
public string RoleName { get; set; }
public string ConferenceRole { get; set; }
}
As I said trying to execute the line foo.Count() returns Null Exception and this might be because the ConferenceRole column allows Null in the database.
Now what I can't understand is that when I invoke the same query directly on the ObjectQuery the Count of records (i.e. invoking foo2.Count()) is returned without any exceptions.
var foo2 = entities.users.Where(fields => fields.ConferenceRole.ToLower().Contains("role"));
Is it possible to the same query above but using the IQueryable usersView object instead?
(It is crucial for me to use the usersView object rather than directly querying the entities.users entity)
EDIT
Below is the code from the PopulateUsersView method
private IQueryable<UsersView> PopulateUsersView()
{
using (EBCPRegEntities entities = new EBCPRegEntities())
{
var users = entities.users.ToList();
List<UsersView> userViews = new List<UsersView>();
foreach (user u in users)
{
userViews.Add(new UsersView()
{
UserId = u.UserId,
Title = u.Title,
Name = u.Name,
Surname = u.Surname,
Street1 = u.Street1,
Street2 = u.Street2,
City = u.City,
PostCode = u.Post_Code,
CountryName = u.country.Name,
WorkPlaceName = u.workplace.Name,
Gender = u.Gender,
EMail = u.E_Mail,
Company = u.Company,
RoleName = u.roles.FirstOrDefault().Name,
ConferenceRole = u.ConferenceRole
});
}
return userViews.AsQueryable();
}
}
Thanks
UPDATE...
Thanks guys I finally found a good answer to the difference between the IQueryable and the ObjectQuery objects.
As a solution I am checking if the ConferenceRole is null and then checking with the contains method as many of you guys have said.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我的猜测是,您的 PopulateUsersView() 方法实际上正在执行查询并返回 IQueryable Linq-to-Objects 对象 - 而 foo2 行仅在SQL层执行查询。如果是这种情况,显然
PopulateUsersView()
将是执行Count
的一种非常低效的方法。要调试此问题:
发布一些代码吗? PopulateUsersView()?
更新
@Ryan - 谢谢用于将代码发布到
PopulateUsersView
看起来我的猜测是正确的 - 您正在执行一个查询,将整个表返回到
List
- 然后您查询这个列表进一步使用Linq2Objects。@ntziolis 为您的问题提供了一种解决方案 - 在执行
ToLower()
之前测试 null。但是,如果您唯一的要求是对非空项目列表进行计数,那么我建议您考虑更改 PopulateUsersView 方法或更改整体设计。如果您需要的只是Count
,那么确保数据库而不是 C# 代码完成这项工作会更有效。如果表有很多行,情况尤其如此 - 例如,您绝对不希望将 1000 行从数据库拉回到内存中。更新 2
请考虑对此进行优化,而不仅仅是进行简单的
!= null
修复。查看您的代码,有几行会导致多次 sql 调用:
CountryName = u.country.Name
WorkPlaceName = u.workplace.Name
RoleName = u .roles.FirstOrDefault().Name
由于这些是在 foreach 循环中调用的,因此要计算大约 500 个用户的数量,那么您可能会进行大约 1501 个 SQL 调用(尽管某些角色和国家/地区)希望会被缓存),总共可能返回一兆字节的数据?这一切只是为了计算一个整数
Count
?My guess is that your
PopulateUsersView()
method is actually executing a query and returning an IQueryable Linq-to-Objects object - while thefoo2
line executes the query only in the SQL layer. If this is the case, the obviouslyPopulateUsersView()
is going to be quite an inefficient way to perform theCount
To debug this:
PopulateUsersView()
?Update
@Ryan - thanks for posting the code to
PopulateUsersView
Looks like my guess was right - you are doing a query which gets the whole table back into a
List
- and its this list that you then query further using Linq2Objects.@ntziolis has provided one solution to your problem - by testing for null before doing the
ToLower()
. However, if your only requirement is toCount
the non-empty items list, then I recommend you look at changing thePopulateUsersView
method or changing your overall design. If all you need is aCount
then it would be much more efficient to ensure that the database does this work and not the C# code. This is espeically the case if the table has lots of rows - e.g. you definitely don't want to be pulling 1000s of rows back into memory from the database.Update 2
Please do consider optimising this and not just doing a simple
!= null
fix.Looking at your code, there are several lines which will cause multiple sql calls:
CountryName = u.country.Name
WorkPlaceName = u.workplace.Name
RoleName = u.roles.FirstOrDefault().Name
Since these are called in a foreach loop, then to calculate a count of ~500 users, then you will probably make somewhere around 1501 SQL calls (although some roles and countries will hopefully be cached), returning perhaps a megabyte of data in total? All this just to calculate a single integer
Count
?在调用ConferenceRole 上的方法之前尝试检查ConferenceRole 是否为null:
这将使您能够在用户视图上调用count 方法。
那么为什么它对
ObjectQuery
起作用呢?当针对 ObjectQuery 执行查询时,LinqToSql 会将您的查询转换为正确的 sql,该 sql 不会出现空值问题,如下所示(它是示例标记 sql,只是实际查询看起来有很大不同,还使用“=”而不是检查for contains):
与 :NET 代码的区别在于:它不会调用对象上的方法,而只是调用方法并传入值,因此在这种情况下不会出现 NullReference。
为了确认这一点,您可以尝试强制 .NET 运行时在调用 where 方法之前执行 SQL,只需在
.Where()ToList()
即可。这应该会导致与您在
UserView
中看到的完全相同的错误。是的,这将首先返回整个用户表,因此不要在实时代码中使用它;)
更新
我必须更新答案,因为我一开始就错误地查询了问题,但上述几点仍然有效。
Try to check whether
ConferenceRole
is null before calling a method on it:This will enable you to call the count method on the user view.
So why does it work against the
ObjectQuery
?When executing the query against the ObjectQuery, LinqToSql is converting your query into proper sql which does not have problems with null values, something like this (it's sample markup sql only the actual query looks much different, also '=' is used rather than checking for contains):
The difference to the :NET code is: It will not call a method on an object but merely call a method and pass in the value, therefore no NullReference can occur in this case.
In order to confirm this you can try to force the .NET runtime to execute the SQL prior to calling the where method, by simply adding a
ToList()
before the.Where()
This should result in the exact same error you have seen with the
UserView
.And yes this will return the entire user table first, so don't use it in live code ;)
UPDATE
I had to update the answer since I c&p the wrong query in the beginning, the above points still stand though.