我有一个表,我们称之为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 个小时,而且我也无法解决这个问题。
非常感谢任何帮助。
问题不清楚。这似乎是一个 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; }
}