使用非空对象属性的EF6查询条件

时间:2016-10-11 14:39:25

标签: c# entity-framework linq entity-framework-6

我有一个来自MVC提交的viewmodel类,我希望根据用户填写的值从EF6获取结果集,但忽略模型中那些为null的项:

public class SearchFilterVM
{
    public int? ID { get; set; }
    public bool? Active { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string MiddleInitial { get; set; }
    public DateTime? DateOfBirth { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
}

基本上我正在寻找的是像这样的伪代码:

var results = context.Members
    .Where(x => x.Active == vm.Active.Value)    // but only if vm prop is not null
    .Where(x => x.FirstName == vm.FirstName)    // but only if vm prop is not null
    .Where(x => x.LastName == vm.LastName)      // but only if vm prop is not null
    .ToList();

(例如,如果过滤器模型属性为null,则完全忽略它们)

当然我不想使用类似上面的内容,因为它会根据第一个标准进行查询,然后根据第二个适用标准等进行重新查询,直到完成(我正在使用500k +行)。< / p>

我想不出为此使用LINQ查询表达式的方法。

我能做的是根据值的存在构建参数化的SQL语句并附加条件,最后通过EF6的RawSQLQuery传递它,这将在性能方面有效(并且可以很好地控制索引字段的顺序)为了更好的调整),但我想知道是否有一种“自然”方式通过LINQ完成同样的事情。

3 个答案:

答案 0 :(得分:5)

我建议的方法

请记住,EF使用延迟执行,并且在您使用ToList()实现任何查询或者例如迭代它之前,您实际上并不执行任何查询。这意味着你可以这样做:

var results = context.Members;

if(vm.Active.HasValue)
{
    results = results.Where(x => x.Active == vm.Active.Value);
}

if(!string.IsNullOrEmpty vm.FirstName))
{
    results = results.Where(x => x.FirstName == vm.FirstName);
}

//and so on until...

return results.ToList();

其他方法

我以为我会额外添加这个额外的免费赠品,以了解为什么你可能不想使用其他答案中提到的技术。让我们说你这样做了:

string name = "bob";

var users = context.Users.Where(u => name == null || u.Name == name).ToList();

这看起来与我的版本非常相似,并且会给出相同的结果,但SQL查询却完全不同。你最终会得到这样的东西:

DECLARE @p__linq__0 NVarChar(1000) = 'bob'
DECLARE @p__linq__1 NVarChar(1000) = 'bob'

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    /* snip */
    FROM [dbo].[Users] AS [Extent1]
    WHERE @p__linq__0 IS NULL OR [Extent1].[Name] = @p__linq__1

请注意,现在在数据库中完成了空检查,并且您还发送了两次参数。好吧,它可能不会明显变慢,但这可能是你未来想要记住的事情。

答案 1 :(得分:3)

您可以在where语句中检查VM变量是否为NULL:

var results = context.Members
    .Where(x => vm.Active == null || x.Active == vm.Active.Value)    // but only if it's not null
    .Where(x => vm.FirstName == null || x.FirstName == vm.FirstName)    // but only if it's not null
    .Where(x => vm.LastName == null || x.LastName == vm.LastName)      // but only if it's not null
    .ToList();

在这种情况下,例如,如果vm.FirstNamenull,则第一个语句vm.FirstName == null将为真(如果不为空,则实际上将检查名字是否相等等等其他条件。

答案 2 :(得分:0)

以下将过滤器组合到1 where子句中,但是当开始枚举结果时,过滤器返回的IQueryable将被构建(并执行),因此不需要太多(例如,通过调用ToList())。

var results = context.Members
                     .Where(x => 
                               (!vm.Active.HasValue()   || x.Active == vm.Active.Value)
                            && (vm.FirstName == null    || x.FirstName == vm.FirstName)
                            && (vm.LastName == null     || x.LastName == vm.LastName))
                     .ToList();