在对象列表中查找下一个值

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

我有一个表,我们称之为quote_lines,像这样:

报价号 产品 生效日期 价格
Q1 ABC 20 年 1 月 1 日 10.00
Q1 ABC 20 年 3 月 1 日 15.00
Q1 ABC 20 年 12 月 1 日 20.00
Q1 ABC 21 年 3 月 1 日 10.00
Q2 ABC 2 月 1 日 13.5

我想创建一个像这样的对象:

public class QuoteLine{
    public string QuoteNo {get;set;}
    public string Product {get;set;}
    public DateOnly DateEffective {get;set;}
    public decimal Price {get;set;}
    public DateOnly NextDate {get;set;}
    public decimal NextPrice {get;set;}
}

问题是,如何获取下一行数据来填充我的对象?例如,当我考虑第一行数据时,我的对象最终会变成

{
    "QuoteNo": "Q1",
    "Product": "ABC",
    "DateEffective": "1-Jan-20",
    "Price": 10,
    "NextDate": "1-Mar-20",
    "NextPrice": 15
}

对于第二行来说是

{
    "QuoteNo": "Q1",
    "Product": "ABC",
    "DateEffective": "1-Mar-20",
    "Price": 15,
    "NextDate": "1-Dec-20",
    "NextPrice": 20
}

等等。

为避免疑义,这将按报价编号和产品进行分组。

如果通过在数据库上创建视图来做到这一点更容易,那很好,但我已经为此苦苦挣扎了大约 4 个小时,而且我也无法解决这个问题。

非常感谢任何帮助。

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

问题不清楚。这似乎是一个 LEAD / LAG 问题,据我所知 EF Core 8 无法轻松解决。还有另一个 ORM 项目linq2db,可以用来填补 EF 的薄弱环节。或者,您可以只编写原始 SQL 查询/视图并使用 EF 执行映射。

linq2db 查询似乎可以解决您的请求。注意:linq2db 可以在没有 EF Core 的情况下用作独立的 ORM。它具有脚手架和 GitHub 自述文件中列出的许多其他功能。

connection
    .GetTable<QuoteLine>()
    .Select(q => new
    {
        q.QuoteNo,
        q.Product,
        q.DateEffective,
        q.Price,
        NextDate = Sql.Ext.Lead(q.DateEffective)
            .Over().PartitionBy(q.QuoteNo).OrderBy(q.DateEffective).ToValue(),
        NextPrice = Sql.Ext.Lead(q.Price)
            .Over().PartitionBy(q.QuoteNo).OrderBy(q.DateEffective).ToValue(),
    })
    .ToList()

// SELECT
//     [q].[QuoteNo],
//     [q].[Product],
//     [q].[DateEffective],
//     [q].[Price],
//     LEAD([q].[DateEffective]) OVER(PARTITION BY [q].[QuoteNo] ORDER BY [q].[DateEffective]),
//     LEAD([q].[Price]) OVER(PARTITION BY [q].[QuoteNo] ORDER BY [q].[DateEffective])
// FROM
//     [quotation_lines] [q]

下面是完整的程序源代码,显示了在 linq2db 中重用 EF Core 模型所需的最低配置和设置。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text.Json;
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

// linq2db Nuget Packages added (License: MIT)
// <PackageReference Include="linq2db" Version="5.4.1" />
// <PackageReference Include="linq2db.EntityFrameworkCore" Version="8.1.0" />

public class Program
{
    public static void Main(string[] args)
    {
        // So you can see the SQL generated by linq2db
        LinqToDB.Data.DataConnection.TurnTraceSwitchOn();
        LinqToDB.Data.DataConnection.WriteTraceLine = (message, _, _) => Console.WriteLine(message);
        
        using (var context = new SalesContext())
        using (var connection = context.CreateLinqToDBConnection())
        {
            var output = connection
                .GetTable<QuoteLine>()
                .Select(q => new
                {
                    q.QuoteNo,
                    q.Product,
                    q.DateEffective,
                    q.Price,
                    NextDate = Sql.Ext.Lead(q.DateEffective)
                        .Over().PartitionBy(q.QuoteNo).OrderBy(q.DateEffective).ToValue(),
                    NextPrice = Sql.Ext.Lead(q.Price)
                        .Over().PartitionBy(q.QuoteNo).OrderBy(q.DateEffective).ToValue(),
                })
                .ToList();

            Console.WriteLine(JsonSerializer.Serialize(output, new JsonSerializerOptions { WriteIndented = true }));
        }
    }
}

public class SalesContext : DbContext
{
    public DbSet<QuoteLine> QuoteLines { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Sales;Trusted_Connection=True");

    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
        => configurationBuilder.Properties<decimal>().HavePrecision(18, 2);
}

[Table("quotation_lines")]
public class QuoteLine
{
    public long Id { get; set; } // EF Needs a PrimaryKey, so I added this
    [MaxLength(100)] public string QuoteNo { get; set; }
    [MaxLength(100)] public string Product { get; set; }
    public DateOnly DateEffective { get; set; }
    public decimal Price { get; set; }
}
© www.soinside.com 2019 - 2024. All rights reserved.