【问题标题】:How to generate a LINQ query that queries all non-null properties from a search page model如何生成从搜索页面模型查询所有非空属性的 LINQ 查询
【发布时间】:2019-04-24 21:43:06
【问题描述】:

我有两个模型: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&lt;TestRecord&gt; 的页面上填写。

如果我只有几个要查询的属性,并且我知道它们肯定会被填充,我可以执行以下操作:

[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 查询,如上所述,该查询从模型中查询所有非空/非空属性?将所有属性硬编码到我的查询中是我唯一的选择吗?

【问题讨论】:

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


    【解决方案1】:

    你就不能这样做吗?

    [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 个大查询中每个字段的输入是否存在?

    【讨论】:

    • 我相信这将是我正在寻找的最简洁的答案。理想情况下,能够在生成查询之前过滤掉所有空属性会很棒,但我想这只是一厢情愿的想法哈哈。我稍后再查看,如果这里没有要添加的内容,我会将其标记为答案
    【解决方案2】:

    查询不必是一个单链,你可以将它拆分并插入一些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();
    

    【讨论】:

      【解决方案3】:

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

      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();
      

      【讨论】:

        【解决方案4】:

        如果您只有一个具有此要求且属性数量有限(例如少于 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 并查看它们中是否有任何一个具有 null 值:

        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;  
        }
        

        【讨论】:

        • 嗨,Harald,感谢您的广泛回答,所以我的意思是将此标记为 .NET Core 问题,因此可能会不赞成使用静态。这里需要进行哪些更改才能将其转换为可以注入到运行查询的控制器中的服务?
        • 我创建的静态函数是扩展方法(google:extension methods demystified)。每个标准 LINQ 函数都是一种扩展方法(查看定义以了解这一点)。如果不能使用静态函数,我想知道如何使用 LINQ。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-05
        相关资源
        最近更新 更多