实体框架,LINQ 查询
Posted
技术标签:
【中文标题】实体框架,LINQ 查询【英文标题】:Entity Framework, LINQ Query 【发布时间】: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) 【参考方案1】: 使用.Include( m => m.AssignmentNoteIdentifiedClasses )
在单个查询中引入相关数据,这比在for-each-row 循环中加载每组相关AssignmentNoteIdentifiedClasses
快得多。
This is also known as the "N+1 Problem" in ORMs。
您不需要使用TruncateTime
。
事实上,您不应该因为这意味着您的查询不是 SARGable。
避免使用 SQL 谓词中的函数。
相反,只需将应用程序代码中的 criteria.FilterStartDate
向下舍入到一天的开始,然后将其与 m => m.CreatedDateTime >= filterStart
进行正常比较。
同样,FilterEndDate
应该向上取整,然后像这样比较:m => m.CreatedDateTime < filterEnd
始终使用独占上限。它使一切,尤其是日期范围谓词,更容易处理。
您不需要在Where
中内联&&
。请改用其他单独的 .Where()
子句。它们将作为单独的 AND
术语添加到同一(单个)WHERE
子句中。
我认为 EF Core 不够复杂,无法识别可选搜索谓词的“NULL
-means-ignore”反模式,在这种情况下不要使用“NULL
-means-忽略" anti-pattern IN AN IQUERYABLE<T>
PREDICATE!
这很糟糕,原因有很多:即因为查询执行计划基于 SQL 查询的 结构(“形状”),而 not 则基于参数值,因此当某些甚至所有参数为NULL
时,将使用非NULL
参数的相同缓存执行计划——这是一个问题。另外,请务必阅读parameter sniffing。
通过使用IQueryable<T>
的 Linq 扩展并重新分配给自身来构建您的查询。
例如IQueryable<T> query = db.Etc; query = query.Where( e => 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<Func<...>>
而不仅仅是Func<>
,这样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<Respositories.AssignmentMasterData>
,但最后一行的ForEachAsync
将返回Task
,因此会出现错误消息。
查看ForEachAsync
签名:
public static System.Threading.Tasks.Task ForEachAsync(这个 System.Linq.IQueryable 源,Action 动作);
其次。你想返回一个IEnumerable<Respositories.AssignmentMasterData>
如果您可以满足于同步方法,您可以这样做:
您需要将您的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<T>
只能同步枚举,不能异步枚举 - 这就是 IAsyncEnumerable<T>
存在的原因。使用非异步 Linq 方法将总是使用同步(即阻塞)枚举。要以非阻塞方式执行此操作,您需要将 AsEnumerable()
替换为 await ...ToListAsync()
并添加 Include( a => a.AssignmentNoteIdentifiedClasses )
- 只有在加载中间结果之后然后您才能同步处理它。
感谢您的解释,但后来我很困惑:原始代码使用了ForEachAsync
,它返回了Task
。如果我理解正确,您会说这是异步的。然后我用我的回答说我们还不知道它是否是异步的,因为我们不知道IEnumerable
将如何使用。如果在这个IEnumerable
上调用ForEachAsync
会怎样。我们不是回到原来的异步情况了吗?
您不能将ForEachAsync
与IEnumerable<T>
一起使用,只能使用IQueryable<T>
(或IAsyncEnumerable<T>
)。它们是完全不同的界面。当您使用 ForEachAsync
和 IQueryable<T>
时,查询将被异步评估。当您将 AsEnumerable()
与 IQueryable<T>
一起使用时,查询将被同步评估(非异步)。以上是关于实体框架,LINQ 查询的主要内容,如果未能解决你的问题,请参考以下文章