【问题标题】:Entity Framework, LINQ Query实体框架,LINQ 查询
【发布时间】:2021-12-25 17:12:40
【问题描述】:

如何遍历下面的项目并将具有空值的字段更新为值 (!) 错误:

无法将 System.threading.task 类型隐式转换为 System.Collections.Generic.IEnumerable

using (SUPEntities db = new SUPEntities())
{
    IEnumerable<AssignementMasterData> masterDatas = null;

    masterDatas = db.AssignementMasterDatas
                    .Where(m => DbFunctions.TruncateTime(m.CreatedDateTime) >= DbFunctions.TruncateTime(criteria.FilterStartDate)
                        && DbFunctions.TruncateTime(m.CreatedDateTime) <= DbFunctions.TruncateTime(criteria.FilterEndDate)
                        && (m.AssignmentNoteNumber == criteria.AssigmentNumber || criteria.AssignmentNumber == null)
                        && (m.BaseCourseId == criteria.courseId || criteria.CourseId == 0)
                        && (m.AccountNumber == criteria.AccountNumber || criteria.AccountNumber == null)
                        && (m.ReferenceNumber == criteria.ReferenceNumber || criteria.ReferenceNumber == null)
                        && (m.FacultyCode == criteria.FAcultyCode || criteria.FacultyCode == null)
                        && (m.Processed == criteria.Processed)
                        && (m.ClassNumber == criteria.ClassNumber || criteria.ClassNumber == null))
                    .ForEachAsync(t => t.AssignmentNoteIdentifiedClasses.Select(e => String.IsNullOrEmpty(e.Category)? "(!)": e.Category));
                
}

【问题讨论】:

  • ForEachAsync 返回 Task 但你没有等待它,你可以使用同步方法(ForEach)

标签: c# entity-framework linq


【解决方案1】:
  • 使用.Include( m =&gt; m.AssignmentNoteIdentifiedClasses ) 在单个查询中引入相关数据,这比在for-each-row 循环中加载每组相关AssignmentNoteIdentifiedClasses 快得多。
  • 您不需要使用TruncateTime
    • 事实上,您不应该因为这意味着您的查询不是 SARGable。
    • 避免使用 SQL 谓词中的函数。
    • 相反,只需将应用程序代码中的 criteria.FilterStartDate 向下舍入到一天的开始,然后将其与 m =&gt; m.CreatedDateTime &gt;= filterStart 进行正常比较。
    • 同样,FilterEndDate 应该向上取整,然后像这样比较:m =&gt; m.CreatedDateTime &lt; filterEnd
      • 始终使用独占上限。它使一切,尤其是日期范围谓词,更容易处理。
  • 您不需要在Where 中内联&amp;&amp;。请改用其他单独的 .Where() 子句。它们将作为单独的 AND 术语添加到同一(单个)WHERE 子句中。
  • 我认为 EF Core 不够复杂,无法识别可选搜索谓词的“NULL-means-ignore”反模式,在这种情况下不要使用“NULL-means-忽略" anti-pattern IN AN IQUERYABLE&lt;T&gt; PREDICATE!
    • 这很糟糕,原因有很多:即因为查询执行计划基于 SQL 查询的 结构(“形状”),而 not 则基于参数值,因此当某些甚至所有参数为NULL 时,将使用非NULL 参数的相同缓存执行计划——这是一个问题。另外,请务必阅读parameter sniffing
    • 通过使用IQueryable&lt;T&gt; 的 Linq 扩展并重新分配给自身来构建您的查询
      • 例如IQueryable&lt;T&gt; query = db.Etc; query = query.Where( e =&gt; etc );
      • 每个.Where() 都作为AND 条件添加。如果你想建立一个OR 条件then use PredicateBuilder

DateTime filterStart   = criteria.FilterStartDate.Date;
DateTime filterEndExcl = criteria.FilterEndDate  .Date.AddDays(1);

using (SUPEntities db = new SUPEntities())
{
    IQueryable<AssignementMasterData> query = db.AssignementMasterDatas
        .Include( m => m.AssignmentNoteIdentifiedClasses )
        .Where( m => m.CreatedDateTime >= filterStart   )
        .Where( m => m.CreatedDateTime <  filterEndExcl ) // Exclusive upper-bound.
        .Where( m => m.Processed       == criteria.Processed )
        .Where( m => m.ClassNumber     == criteria.ClassNumber )
    ;

    if( criteria.AssigmentNumber != null )
    {
        query = query.Where( m => m.AssignmentNoteNumber == criteria.AssigmentNumber );
    }

    if( criteria.AccountNumber != null )
    {
        query = query.Where( m => m.AccountNumber == criteria.AccountNumber );
    }

    if( criteria.CourseId != null && criteria.CourseId.Value > 0 )
    {
        query = query.Where( m => m.BaseCourseId == criteria.CourseId );
    }

    if( criteria.ReferenceNumber != null )
    {
        query = query.Where( m => m.ReferenceNumber == criteria.ReferenceNumber );
    }

    if( criteria.FacultyCode != null )
    {
        query = query.Where( m => m.FacultyCode == criteria.FacultyCode );
    }

    if( criteria.ClassNumber != null )
    {
        query = query.Where( m => m.ClassNumber == criteria.ClassNumber );
    }

    List<AssignementMasterData> rows = await query.ToListAsync().ConfigureAwait(false);
    
    List<String> categories = rows
        .SelectMany( r => r.AssignmentNoteIdentifiedClasses )
        .Select( String.IsNullOrEmpty(e.Category)? "(!)": e.Category) )
        .ToList();

    return categories;
}

上面可以通过添加一个新的扩展方法来简化(确保你使用Expression&lt;Func&lt;...&gt;&gt;而不仅仅是Func&lt;&gt;,这样EF仍然可以解释查询:

public static class MyQueryableExtensions
{
    public static IQueryable<T> WhereIfNotNull<T,TValue>( this IQueryable<T> query, TValue? value, Expression<Func<T,Boolean>> predicate )
        where TValue : struct
    {
        if( value.HasValue && value.Value != default(TValue) )
        {
            return query.Where( predicate );
        }
        else
        {
            return query;
        }
    }
}

这样使用:

// `criteria` is now named `c` for brevity.

DateTime filterStart   = c.FilterStartDate.Date;
DateTime filterEndExcl = c.FilterEndDate  .Date.AddDays(1);

using (SUPEntities db = new SUPEntities())
{
    IQueryable<AssignementMasterData> query = db.AssignementMasterDatas
        .Include( m => m.AssignmentNoteIdentifiedClasses )
        .Where( m => m.CreatedDateTime >= filterStart    )
        .Where( m => m.CreatedDateTime <  filterEndExcl  ) // Exclusive upper-bound.
        .Where( m => m.Processed       == c.Processed    )
        .Where( m => m.ClassNumber     == c.ClassNumber  )
        .WhereIfNotNull( c.AssigmentNumber, m => m.AssignmentNoteNumber == c.AssigmentNumber )
        .WhereIfNotNull( c.AccountNumber  , m => m.AccountNumber        == c.AccountNumber   )
        .WhereIfNotNull( c.CourseId       , m => m.BaseCourseId       ​  == c.CourseId        )
        ​.WhereIfNotNull( c.ReferenceNumber, m => m.ReferenceNumberr     == c.ReferenceNumber )
        ​.WhereIfNotNull( c.FacultyCode    , m => m.FacultyCoder         == c.FacultyCode     )
        ​.WhereIfNotNull( c.ClassNumber    , m => m.ClassNumber          == c.ClassNumber     )
   ;

    List<AssignementMasterData> rows = await query.ToListAsync().ConfigureAwait(false);
    
    List<String> categories = rows
        .SelectMany( r => r.AssignmentNoteIdentifiedClasses )
        .Select( String.IsNullOrEmpty(e.Category)? "(!)": e.Category) )
        .ToList();

    return categories;
}

【讨论】:

    【解决方案2】:

    首先,关于错误信息:

    您试图将错误的类型分配给您的 masterDatas 变量。 您将其声明为IEnumerable&lt;Respositories.AssignmentMasterData&gt;,但最后一行的ForEachAsync 将返回Task,因此会出现错误消息。

    查看ForEachAsync签名:

    public static System.Threading.Tasks.Task ForEachAsync(这个 System.Linq.IQueryable 源,Action 动作);

    其次。你想返回一个IEnumerable&lt;Respositories.AssignmentMasterData&gt;

    如果您可以满足于同步方法,您可以这样做:

    您需要将您的IQueryable 转换为IEnumerable。致电AsEnumerable() 即可。然后你需要替换一些值。所以你需要使用Select来投射你的收藏。

    using (SUPEntities db = new SUPEntities())
    {
        var masterDatas = db.AssignementMasterDatas
            .Where(m => DbFunctions.TruncateTime(m.CreatedDateTime) >= DbFunctions.TruncateTime(criteria.FilterStartDate)
                && DbFunctions.TruncateTime(m.CreatedDateTime) <= DbFunctions.TruncateTime(criteria.FilterEndDate)
                && (m.AssignmentNoteNumber == criteria.AssigmentNumber || criteria.AssignmentNumber == null)
                && (m.BaseCourseId == criteria.courseId || criteria.CourseId == 0)
                && (m.AccountNumber == criteria.AccountNumber || criteria.AccountNumber == null)
                && (m.ReferenceNumber == criteria.ReferenceNumber || criteria.ReferenceNumber == null)
                && (m.FacultyCode == criteria.FAcultyCode || criteria.FacultyCode == null)
                && (m.Processed == criteria.Processed)
                && (m.ClassNumber == criteria.ClassNumber || criteria.ClassNumber == null))
            .AsEnumerable()
            .Select(a =>
            {
                a.AssignmentNoteIdentifiedClasses = a.AssignmentNoteIdentifiedClasses
                    .Select(e =>
                    {
                        e.Category = string.IsNullOrWhiteSpace(e.Category) ? "(!)" : e.Category;
                        return e;
                    })
                    .ToList(); // Depending on the type of AssignmentNoteIdentifiedClasses, ToList() might be replaced.
                return a;
            });
        return masterDatas;
    }
    

    【讨论】:

    • 当然,现在这段代码阻塞了线程而不是让它保持异步。
    • 这将取决于枚举返回的 IEnumerable 的方式。
    • "这将取决于返回的IEnumerable 的枚举方式。" - 不,这是不正确的。 IEnumerable&lt;T&gt;只能同步枚举,不能异步枚举 - 这就是 IAsyncEnumerable&lt;T&gt; 存在的原因。使用非异步 Linq 方法将总是使用同步(即阻塞)枚举。要以非阻塞方式执行此操作,您需要将 AsEnumerable() 替换为 await ...ToListAsync() 并添加 Include( a =&gt; a.AssignmentNoteIdentifiedClasses ) - 只有在加载中间结果之后然后您才能同步处理它。
    • 感谢您的解释,但后来我很困惑:原始代码使用了ForEachAsync,它返回了Task。如果我理解正确,您会说这是异步的。然后我用我的回答说我们还不知道它是否是异步的,因为我们不知道IEnumerable 将如何使用。如果在这个IEnumerable 上调用ForEachAsync 会怎样。我们不是回到原来的异步情况了吗?
    • 不能ForEachAsyncIEnumerable&lt;T&gt; 一起使用,只能使用IQueryable&lt;T&gt;(或IAsyncEnumerable&lt;T&gt;)。它们是完全不同的界面。当您使用 ForEachAsyncIQueryable&lt;T&gt; 时,查询将被异步评估。当您将 AsEnumerable()IQueryable&lt;T&gt; 一起使用时,查询将被同步评估(异步)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多