我有一个包含两个表的层次结构:
CREATE TABLE A (
Id nvarchar(50) NOT NULL,
ChildrenCount int NOT NULL
)
CREATE TABLE B (
ParentId nvarchar(50) NOT NULL,
I int NOT NULL,
OtherColumns text NULL,
AFlag bit NOT NULL
)
在表 A 中,我有一个
ChildrenCount
属性,指示最终将出现在表 B 中的相关记录的数量,因此此时其中一些记录可能会丢失,请注意,这不是计算出有多少个孩子的值存在于数据库中。事实上,我们的目标是获取每位家长丢失的记录。
这是两个表中的数据示例:
身份证 | 儿童数 |
---|---|
AA | 1 |
BB | 5 |
抄送 | 3 |
DD | 1000 |
家长ID | 我 | A专栏 | AF标志 |
---|---|---|---|
BB | 0 | 文字 | 0 |
BB | 2 | 文字 | 1 |
BB | 4 | 空 | 0 |
抄送 | 0 | 空 | 1 |
抄送 | 1 | 空 | 0 |
抄送 | 2 | 文字 | 1 |
生成的 SQL 查询应该如下所示: |Id|I |ParentId |I |AColumn |AFlag| |- |- |- |- |- |-| |AA|0 |NULL |NULL |NULL |NULL| |BB|0 |BB |0 |文本 |0 | |BB|1 |NULL |NULL |NULL |NULL | |BB|2 |BB |2 |文本 |1 | |BB|3 |NULL |NULL |NULL |NULL | |BB|4 |BB |4 |NULL |0 | |CC|0 |CC |0 |NULL |1 | |CC|1 |CC |1 |NULL |0 | |CC|2 |CC |2 |文本 |1 | |DD|0 |NULL |NULL |NULL |NULL | |DD|1 |NULL |NULL |NULL |NULL | |~ |~ |~ |~ |~ |~ | |DD|999 |空 |空 |空 |空 |
我正在使用实体框架,我正在寻找一种更简洁的解决方案来维护此 C# 代码,并让我使用 LINQ 生成更复杂的查询。
var sequenceQuery = "<SOME RAW SQL>";
var query = dbc.Set<A>()
.SelectMany(a => dbc.Set<Int32Record>()
.FromSqlRaw(sequenceQuery)
.Where(i => i.Value < a.ChildrenCount)
.Select(i => new { a, i }))
.GroupJoin(dbc.Set<B>(),
x => new { ParentId = x.a.Id, I = x.i.Value },
x => new { x.ParentId, x.I },
(x, bs) => new { x.a, I = x.i.Value, Bs = bs.ToArray() })
.SelectMany(x => x.Bs.DefaultIfEmpty(), (x, b) => new { A = x.a, x.I, B = b });
问题是如何生成整数序列,无论是使用递归查询还是使用 T-SQL 函数
generate_series
,以及如何在 EF 中使用它:
SQL 中的第一种方法是递归查询:
var sequenceQuery = """
WITH seq(i) AS (
SELECT 0
UNION ALL
SELECT seq.i + 1 FROM seq WHERE (seq.i + 1) < 32767
)
SELECT seq.i FROM seq OPTION (MAXRECURSION 32767)
"""
问题是WITH子句要求它包装查询的其余部分,并且它不适合与
FromSqlRaw
一起使用。
另一种可能的解决方案是使用 T-SQL 函数
generate_series
:
var sequenceQuery = """
SELECT value
FROM generate_series(0, (SELECT MAX(childrenCount) FROM A) - 1)
""";
生成的 sql 查询将类似于以下内容:
SELECT A.Id, s.I, B.*
FROM A
JOIN (SELECT value AS I FROM generate_series(0, (SELECT MAX(ChildrenCount) FROM A))) s
ON s.I < ChildrenCount
LEFT JOIN B ON B.ParentId = A.Id AND B.I = s.I
ORDER BY A.Id, s.I
它确实获得了正确的数据,但我一直在寻找一种在数据库发生更改时不会出现问题的解决方案。
编辑:我对示例做了一些更改,因为前一个示例具有误导性。
您可以在模型中创建自定义表值函数映射
public IQueryable<int> GenerateSeries(int start, int stop, int step = 1)
=> FromExpression(() => GenerateSeries(start, stop, step));
在您的配置中
modelBuilder.HasDbFunction(typeof(MyDbContext).GetMethod(nameof(GenerateSeries), new[] { typeof(int), typeof(int), typeof(int) }));
现在你可以做
var results =
from a in db.A
from v in db.GenerateSeries(1, a.childrenCount)
join b in db.B on new { a.id, v } equals { b.parentId, b.i } into g1
from b in g1.DefaultIfEmpty()
orderby a.id, v
select new {
a.id,
value: v,
b.i
};