EF Core 8 - 应用客户端投影后无法转换设置操作

问题描述 投票:0回答:1

我正在尝试对 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”子句,但我不断收到错误。 有人可以建议我可以尝试的其他事情吗?

c# .net-core entity-framework-core
1个回答
0
投票

有一些 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。首先将原始值投影为匿名类型,然后执行第二个内存中投影,该投影执行无法转换以获得最终结果的步骤。

© www.soinside.com 2019 - 2024. All rights reserved.