GroupBy,之后投影到没有.ToList()的领域模型中

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

我正在尝试将分组模型转换为使用.Include()连接到另一个模型的域模型后使用group by。

我们的应用程序使用三层:数据,业务逻辑,服务(Web API)。这些层使用域模型进行通信,api使用数据传输对象与客户端进行通信。我们使用实体框架核心3.1.3进行数据库访问。

api允许使用查询参数进行过滤,排序,计数和分组。我们有一个库将这些请求转换为LINQ表达式,并将其应用于IQueryable<DomainModel>。为了使这些查询在我们的数据库中执行,我们在Select处使用IQueryable创建投影。

现在,我们得到了一对多关系的两个数据模型。

public class InvoiceDataModel
{
    public int InvoiceId { get; set; }

    public int InvoiceNumber { get; set; }

    public DateTime InvoiceDate { get; set; }

    public DateTime DueDate { get; set; }

    public int CustomerNumber { get; set; }

    public string CustomerName { get; set; }

    public IEnumerable<DocumentDataModel> Documents { get; set; }
}

public class DocumentDataModel
{
    public int DocumentId { get; set; }

    public string DocumentNumber { get; set; }

    public string Type { get; set; }

    public string Category { get; set; }

    public InvoiceDataModel Invoice { get; set; }
}

public class DataContext : DbContext
{
    public DbSet<InvoiceDataModel> Invoice { get; set; }

    public DbSet<DocumentDataModel> Documents { get; set; }

    public DataContext(DbContextOptions<DataContext> options)
        : base(options)
    {}

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<InvoiceDataModel>(c =>
        {
            c.ToTable("Invoice");
            c.HasKey(p => p.InvoiceId);
            c.HasMany(p => p.Documents)
                .WithOne(p => p.Invoice);
        });

        modelBuilder.Entity<DocumentDataModel>(c =>
        {
            c.ToTable("Document");
            c.HasKey(p => p.DocumentId);
        });
    }
}

以及相应的领域模型

public class InvoiceDomainModel
{
    public int InvoiceId { get; set; }

    public int InvoiceNumber { get; set; }

    public DateTime InvoiceDate { get; set; }

    public DateTime DueDate { get; set; }

    public CustomerDomainModel Customer { get; set; }

    public IEnumerable<DocumentDomainModel> Documents { get; set; }
}

public class DocumentDomainModel
{
    public int DocumentId { get; set; }

    public string DocumentNumber { get; set; }

    public string Type { get; set; }

    public string Category { get; set; }
}

public class CustomerDomainModel
{
    public int CustomerNumber { get; set; }

    public string CustomerName { get; set; }
}

过滤,排序和计数对此适用。分组抛出InvalidCastException: Unable to cast object of type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlFunctionExpression' to type 'System.Linq.Expressions.ConstantExpression'.

为了找到问题所在,我决定自己编写LINQ

_dataContext.Invoice
    .Include(p => p.Documents)
    .Select(x => new InvoiceDomainModel
    {
        InvoiceId = x.InvoiceId,
        InvoiceNumber = x.InvoiceNumber,
        DueDate = x.DueDate,
        InvoiceDate = x.InvoiceDate,
        Customer = new CustomerDomainModel
        {
            CustomerName = x.CustomerName,
            CustomerNumber = x.CustomerNumber
        },
        // Adding this does throw
        // InvalidCastException: Unable to cast object of type
        // 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlFunctionExpression' to type
        // 'System.Linq.Expressions.ConstantExpression'.
        Documents = x.Documents.Select(d => new DocumentDomainModel
        {
            DocumentId = d.DocumentId,
            DocumentNumber = d.DocumentNumber,
            Category = d.Category,
            Type = d.Type
        })
    })
    // Done at logic
    .GroupBy(p => p.InvoiceNumber)
    .Select(g => new {g.Key, Count = g.Count()})
    .ToList();

问题似乎是我们域模型投影中的文档选择问题。删除它可以使代码生效。

我也可以在分组之前添加.ToList()来解决此问题。但是,这将在大量的性能问题中泄漏,因为数据库包含超过1.5亿个条目,并且所有这些条目都将被加载到内存中。这不是我们的选择。

我们是否遇到实体框架核心和linq的限制?

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

我们是否遇到实体框架核心和linq的限制?

[很遗憾,您遇到的是EF Core 3.x GroupBy翻译错误。他们的GitHub问题跟踪器#20887 GroupBy causes exception中有一个类似的问题,目前,恕我直言,IMHO错误地将其关闭为#19929 Query: Support GroupBy when it is final operator的副本,因此我在此处发布了有关此帖子的评论。

目前的解决方法(不确定是否以及如何将其插入查询管道中)是预先选择(将其分为匿名类型或相同类型)对键/聚集进行分组所需的列,例如在GroupBy之前插入以下内容:

.Select(p => new { p.InvoiceNumber })

.Select(p => new InvoiceDomainModel { InvoiceNumber = p.InvoiceNumber })
© www.soinside.com 2019 - 2024. All rights reserved.