我正在尝试对 2 个不同的表进行联合,但自从更新到 EF Core 8 以来我开始收到此错误。完整的错误消息是:
InvalidOperationException:无法在之后转换设置操作 已应用客户投影。考虑移动集合操作 在最后一次“选择”调用之前。
这是我的代码:
var query = dbContext.Servicing
.Include(s => s.Customer)
.Include(s => s.Pictures)
.Where(s => s.CustomerId == customerId)
.Where(i => i.Pictures.Count > 0)
.Select(s => new SiteVisit
{
Id = s.Id,
CustomerId = s.CustomerId,
CustomerName = s.Customer.Name,
Date = s.ServiceDate,
Category = SiteVisitCategory.Service,
ServicingPictures = s.Pictures,
InstallPictures = new List<Picture>(),
})
.Union(dbContext.Installs
.Include(i => i.Order)
.Include(i => i.Pictures)
.Where(i => i.Order.CustomerId == customerId)
.Where(i => i.Pictures.Count > 0)
.Select(i => new SiteVisit
{
Id = i.Id,
CustomerId = i.Order.CustomerId,
CustomerName = i.Order.CustomerName,
Date = i.InstallDate,
Category = SiteVisitCategory.Install,
InstallPictures = i.Pictures,
ServicingPictures = new List<Picture>()
}))
.OrderByDescending(v => v.Date)
.AsNoTracking();
var queryResults = await query.ToListAsync();
我尝试移动“Where”子句,但我不断收到错误。 有人可以建议我可以尝试的其他事情吗?
有一些 EF 无法通过联合解决的问题,虽然能够在单个查询中获取所有信息似乎很好,但您最好的选择很可能是提取单独查询中的项目,在内存中组合结果(因为您无论如何都在加载数据)或者可能联合基础数据并单独获取图像。当使用投影到此站点访问 DTO/ViewModel 时,我强烈建议的一个细节是避免混合 DTO 和实体。在本例中,存储对 SiteVisit 中的图片实体的引用。理想情况下,将这些内容投影到 POCO DTO 类,并仅包含您需要的 Picture 中的字段。即使需要大多数字段,也可以避免在处理图片时产生混乱,因为它是一个被跟踪的实体,还是一些源自实体的独立且可能反序列化的表示。
选项 1:内存联合:
var serviceSiteVisits = await dbContext.Servicing
.Where(s => s.CustomerId == customerId)
.Where(i => i.Pictures.Count > 0)
.Select(s => new SiteVisit
{
Id = s.Id,
CustomerId = s.CustomerId,
CustomerName = s.Customer.Name,
Date = s.ServiceDate,
Category = SiteVisitCategory.Service,
ServicingPictures = s.Pictures.Select(p => new PictureDto
{
Id = p.Id,
// ..
}).ToList()
}).ToListAsync();
var installSiteVisits = await dbContext.Installs
.Where(i => i.Order.CustomerId == customerId)
.Where(i => i.Pictures.Count > 0)
.Select(i => new SiteVisit
{
Id = i.Id,
CustomerId = i.Order.CustomerId,
CustomerName = i.Order.CustomerName,
Date = i.InstallDate,
Category = SiteVisitCategory.Install,
InstallPictures = i.Pictures.Selectp => new PictureDto
{
Id = p.Id,
// ..
}).ToList()
}).ToListAsync();
var results = serviceSiteVisits.Concat(installSiteVisits)
.OrderByDescending(v => v.Date)
.ToList();
在您的 SiteVisit ViewModel(和实体)中,集合引用会自动初始化,以避免投影/总体中的代码负责初始化。例如,实体永远不应该公开集合的公共设置器,因为滥用这些设置器可能会扰乱更改跟踪。例如:
public ICollection<PictureDto> ServicingPictures { get; protected set; } = new List<PictureDto>();
选项 2:联合双投影并单独加载图片。
var initialResults = await dbContext.Servicing
.Where(s => s.CustomerId == customerId)
.Where(i => i.Pictures.Count > 0)
.Select(s => new
{
Id = s.Id,
CustomerId = s.CustomerId,
CustomerName = s.Customer.Name,
Date = s.ServiceDate,
Category = SiteVisitCategory.Service,
ServicingPictureIds = s.Pictures.Select(p => p.Id).ToList(),
InstallPictureIds = new List<int>(),
})
.Union(dbContext.Installs
.Where(i => i.Order.CustomerId == customerId)
.Where(i => i.Pictures.Count > 0)
.Select(i => new
{
Id = i.Id,
CustomerId = i.Order.CustomerId,
CustomerName = i.Order.CustomerName,
Date = i.InstallDate,
Category = SiteVisitCategory.Install,
InstallPictureIds = i.Pictures.Select(p => p.Id).ToList(),
ServicingPictureIds = new List<int>()
})).OrderByDescending(v => v.Date)
.ToListAsync();
var pictureIds = initialResults.SelectMany(x => x.ServicePictureIds)
.Concat(initialResults.SelectMany(x => x.InstallPictureIds))
.ToList();
var pictures = await dbContext.Pictures
.Where(p => pictureIds.Contains(p.Id))
.Select(p => new PictureDto
{
Id = p.Id,
// ...
}).ToListAsync();
var results = interimResults
.Select(x => new SiteVisit
{
Id = x.Id,
CustomerId = x.CustomerId,
CustomerName = x.CustomerName,
Date = x.Date,
Category = x.Category,
ServicingPictures = x.ServicingPictureIds
.Select(pId => pictures.FirstOrDefault(p => p.Id == pId))
.ToList();
InstallPictures = x.InstallPictureIds
.Select(pId => pictures.FirstOrDefault(p => p.Id == pId))
.ToList();
}).ToList();
老实说,写完所有内容后,它可能仍然不起作用,所以我建议坚持使用更简单的选项 1。:) 双投影可用于帮助摆脱存在属性或 EF 无法转换的棘手情况深入到 SQL。首先将原始值投影为匿名类型,然后执行第二个内存中投影,该投影执行无法转换以获得最终结果的步骤。