我在使用linq查询时遇到问题,我尝试从中获取某些信息,而根本找不到问题所在。我的ef核心数据库中有工具,任务和M-N ToolTask实体。该查询在末尾包括2个左外部联接和一个group by。
即使我正在检查分组的值是否不为空,我仍然收到诸如“可为空的对象必须具有值”之类的错误消息。我在这里想念什么?
而且看来此linq查询是在客户端评估的,我能做些什么使其成为服务器端?由于某种原因,当我在代码中没有“ let maxDateV ...”行时,它已经在服务器端。
var max = (from t in _fabContext.Tools
join tt in _fabContext.ToolTask on t.ToolId equals tt.ToolId into tt1
from tt in tt1.DefaultIfEmpty()
join ts in _fabContext.Tasks on tt.TaskId equals ts.TaskId into ts1
from ts in ts1.DefaultIfEmpty()
group tt by tt.ToolId into g
let maxOrderV = g.Max(c => c != null ? c.Order : 0)
let maxDateV = g.Max(c => c != null ? c.Task.ExportDate : DateTime.MinValue)
select new
{
ToolId = g.Key,
MaxOrder = maxOrderV,
MaxExportDate = maxDateV
}).ToDictionary(d => d.ToolId, d =>
new OrderExportDate {
Order = d.MaxOrder,
ExportDate = d.MaxExportDate
});
更新1(实体类):
任务
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Main.DataLayer.EfClasses
{
public class Task
{
public int TaskId { get; private set; }
[Required]
public string Name { get; private set; }
[Required]
public int ProfileId { get; private set; }
[Required]
public DateTime ExportDate { get; private set; }
private HashSet<ToolTask> _toolTask;
public IEnumerable<ToolTask> ToolTask => _toolTask?.ToList();
private Task()
{
}
public Task(
string name,
int profileId,
DateTime exportDate)
{
Name = name;
ProfileId = profileId;
ExportDate = exportDate;
}
}
}
ToolTask
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Main.DataLayer.EfClasses
{
public class ToolTask
{
public int ToolId
{
get; private set;
}
public Tool Tool
{
get; private set;
}
public int TaskId
{
get; private set;
}
public Task Task
{
get; private set;
}
[Required]
public int SortOrder
{
get; private set;
}
private ToolTask() { }
internal ToolTask(Tool tool, Task task, int sortOrder)
{
Tool = tool;
ToolId = tool.ToolId;
Task = task;
TaskId = task.TaskId;
SortOrder = sortOrder;
}
public void ChangeOrder(int order)
{
SortOrder = order;
}
}
}
工具
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Main.DataLayer.EfCode;
namespace Main.DataLayer.EfClasses
{
public class Tool
{
public int ToolId
{
get; private set;
}
[Required]
public string Name
{
get; private set;
}
[Required]
public string Model
{
get; private set;
}
private HashSet<ToolTask> _toolTask;
public IEnumerable<ToolTask> ToolTask => _toolTask?.ToList();
internal Tool() { }
public Tool(string name, string model)
{
Name = name;
Model = model;
_toolTask = new HashSet<ToolTask>();
}
public void AddTask(Task task, int sortOrder)
{
_toolTask?.Add(new ToolTask(this, task, sortOrder));
}
}
}
每个实体类都有自己的配置类,但是除了为备份字段设置主键和访问模式外,没有什么其他重要的东西。
答案 0 :(得分:2)
该实现的问题是您按tt.ToolId
分组,其中tt
来自左外部联接的“右侧”,因此tt
可以是{{1 }}。
您可以通过使用左侧的相应字段轻松地解决此问题,例如null
。
但这不会使查询完全可翻译。为此,在构建LINQ查询时(至少使用当前的EF Core查询转换器)需要遵循一些规则:
group tt by t.ToolId into g
查询,请始终使用GroupBy
构造的{element}
部分来预先选择聚合方法中所需的所有表达式。group {element} by {key}
,即使数据类型不可为空并且来自联接的可选端。但不是在C#中,因此您必须使用cast指定显式。null
和Min
的可为空的重载。如果需要,将结果(不是操作数)转换为哨兵值。但最好保留它Max
和可为空的类型。避免使用null
,因为它在SqlServer中没有表示形式。将它们应用于您的查询:
DateTime.MinValue
可以很好地转换为单个SQL查询
var max =
(from t in _fabContext.Tools
from tt in t.ToolTask.DefaultIfEmpty()
let ts = tt.Task
group new
{
SortOrder = (int?)tt.SortOrder,
ExportDate = (DateTime?)ts.ExportDate
}
by new { t.ToolId }
into g
select new
{
ToolId = g.Key.ToolId,
MaxOrder = g.Max(e => e.SortOrder) ?? 0,
MaxExportDate = g.Max(e => e.ExportDate)
})
.ToDictionary(d => d.ToolId, d => new OrderExportDate
{
Order = d.MaxOrder,
ExportDate = d.MaxExportDate ?? DateTime.MinValue
});
这正是具有SQL背景的人所期望的。
如果您不关心转换后的SQL查询的形状,则可以避免group by,而只需以一种简单的逻辑方式编写LINQ查询即可:
SELECT [t].[ToolId], MAX([t.ToolTask].[SortOrder]) AS [MaxOrder], MAX([t.ToolTask.Task].[ExportDate]) AS [MaxExportDate]
FROM [Tool] AS [t]
LEFT JOIN [ToolTask] AS [t.ToolTask] ON [t].[ToolId] = [t.ToolTask].[ToolId]
LEFT JOIN [Task] AS [t.ToolTask.Task] ON [t.ToolTask].[TaskId] = [t.ToolTask.Task].[TaskId]
GROUP BY [t].[ToolId]