如何生成一个LINQ查询,该查询从搜索页面模型查询所有非null属性

时间:2019-04-24 21:43:06

标签: c# linq asp.net-core linq-to-sql

我有两个模型:TestRecordPart,其中TestRecord包含对Part的引用:

TestRecord
----
public string TestRecordId
public int PartId <-- foreign key to parts table
public string Name
public string TestType
public virtual Part Part

Part
----
int PartId
string Name
string Description
public virtual ICollection<TestRecord> TestRecords

我有一个搜索页面,其中显示测试记录的每个属性的输入,包括其相关属性,例如:

@model TestRecord
<!-- From TestRecord -->
<input asp-for="TestType" type="text" />
<!-- From TestRecord.Part assoc. prop -->
<input asp-for="Part.Name" type="text" />
....
And so on...

将其发布到控制器以运行查询时,处理此查询的最佳方法是什么?我有20多个属性,这些属性可能会或可能不会在页面上填写,这有助于过滤查询以返回List<TestRecord>

如果我只有几个要查询的属性,并且知道可以保证填充了这些属性,则可以执行以下操作:

[HttpPost]
public List<TestRecord> Search(TestRecord testRecord){

    List<TestRecord> records = _db.TestRecords
                     .Where(tr => tr.TestType == testRecord.TestType)
                     .Where(tr => tr.Part.Name == testRecord.Part.Name).ToList();

    return records;
}

如上所述,我将如何生成LINQ查询,以查询模型中所有非null / empty的属性?我唯一的选择是将所有属性硬编码到查询中吗?

4 个答案:

答案 0 :(得分:2)

你不能做这样的事情吗?

[HttpPost]
public List<TestRecord> Search(TestRecord testRecord){

    List<TestRecord> records = _db.TestRecords
                 .Where(tr => String.IsNullOrEmpty(testRecord.TestType) ? true : tr.TestType == testRecord.TestType)
                 .Where(tr => String.IsNullOrEmpty(testRecord.Part.Name) ? true : tr.Part.Name == testRecord.Part.Name)
                 //etc...
                 .ToList();

    return records;
}

本质上仅在1个大查询中每个字段都有输入的情况下进行过滤吗?

答案 1 :(得分:0)

查询不必是一个单一的链,您可以将其拆分并插入一些if

var query = _db.TestRecords.AsQueryable();
if (string.IsNullOrEmpty(testRecord.TestType))
{
    query = query.Where(x => x.TestType == testRecord.TestType);
}
if (string.IsNullOrEmpty(testRecord.Part.Name))
{
    query = query.Where(x => x.Part.Name == testRecord.Part.Name);
}

// or, you can use an intermediate variable before returning to debug
return query.ToList();

答案 2 :(得分:0)

我通常使用这样的扩展方法:

public static IQueryable<T> Where<T>(this IQueryable<T> that, object notNull, Expression<Func<T, bool>> predicate)
{
    if (!string.IsNullOrWhiteSpace(notNull?.ToString()))
    {
        return that.Where(predicate);
    }

    return that;
}

然后,您可以像这样编写LINQ查询:

return s.Query()
    .Where(onlyStatus, p => p.Status == onlyStatus)
    .OrderByDescending(p => p.CreatedDate)
    .ToList();

答案 3 :(得分:0)

如果只有一个具有此要求的类,且该属性仅具有有限数量的属性(例如少于20个),那么我不会为这个创建通用解决方案。编写一个Where来检查所有属性。这样做的好处是,如果将来有人更改或删除属性,编译器会抱怨。

一个不错的解决方案是为您的类提供扩展功能:

public static bool HasNullProperties(this MyClass x)
{
    return x.Name == null
        && x.Location == null
        && x.OrderSize == null
        ...;
}

public static IEnumerable<MyClass> WhereHasNullProperties(this IEnumerable<MyClass> source)
{
    return source.Where(item => item.HasNullProperties();
}

在LINQ语句中的某处使用

var result = dbContext.MyItems.WhereHasNullProperties()
    .GroupBy(...)
    .Select(...);

如果您想要适用于多个类的完整的解决方案,请考虑设计一个接口:

interface IHasNullProperties
{
    bool HasNullProperties {get;}
}

您的LINQ函数将是:

 public static IEnumerable<TSource> WhereHasNullProperties<TSource>(
      this IEnumerable<TSource> source)
      where TSource : IHasNullProperties
{
    return source.Where(item => item.HasNullProperties();
}

最后一个相对较慢的方法是使用反射:对于任何类,获取其所有可为空的get-properties,并查看其中是否有空值:

static bool HasNullPrperties<TSource>(this TSource source)
    where TSource : class
{
     // Take the type of the source, and get all properties of this type
     var result = source.GetType().GetProperties()

         // keep only the readable properties (so you can do GetValue)
         // and those properties that have a nullable type
         .Where(property => property.CanRead 
             && Nullable.GetUnderlyingType(property.Type) != null)

         // for every of this properties, ask the source object for the property value:
        .Select(property => property.GetValue(source))

        // and keep only the properties that have a null value
        .Where(value => value == null);

        // return true if source has any property with a null value
        // = if there is any value left in my sequence
        .Any();
    return result;  
}