让分页与一对多连接一起工作

Getting pagination to work with one to many join

我目前正在处理一个具有多个一对多和多对多关系的数据库,我正在努力让 ormlite 很好地工作。

我有这样的一对多关系:

var q2 = Db.From<GardnerRecord>()
    .LeftJoin<GardnerRecord, GardnerEBookRecord>((x, y) => x.EanNumber == y.PhysicalEditionEan)

我需要 return ProductDto 的集合,其中包含 GardnerEBookRecord.

的嵌套列表

使用 SelectMult() 技术它不起作用,因为分页中断,因为我将左连接结果压缩到较小的集合,因此页面大小和偏移量都是错误的(此方法:)

为了获得正确的分页,我需要能够执行类似的操作:

select r.*, count(e) as ebook_count, array_agg(e.*)
from gardner_record r
       left join gardner_e_book_record e
                 on r.ean_number = e.physical_edition_ean
group by r.id

文档中没有这方面的例子,我一直在努力弄清楚。我在 OrmLite 的 Sql 对象中看不到任何功能类似于 array_agg 的东西。

我试过以下变体:

var q2 = Db.From<GardnerRecord>()
    .LeftJoin<GardnerRecord, GardnerEBookRecord>((x, y) => x.EanNumber == y.PhysicalEditionEan)
    .GroupBy(x => x.Id).Limit(100)
    .Select<GardnerRecord, GardnerEBookRecord>((x, y) => new { x, EbookCount = Sql.Count(y), y }) //how to aggregate y?

var res2 = Db.SelectMulti<GardnerRecord, GardnerEBookRecord>(q2);

var q2 = Db.From<GardnerRecord>()
    .LeftJoin<GardnerRecord, GardnerEBookRecord>((x, y) => x.EanNumber == y.PhysicalEditionEan)
    .GroupBy(x => x.Id).Limit(100)
    .Select<GardnerRecord, List<GardnerEBookRecord>>((x, y) => new { x, y });

var res = Db.SqlList<object>(q2);

但我不知道如何将 GardnerEBookRecord 聚合到列表中并保持分页和偏移正确。

这可能吗?任何解决方法?

编辑:

我做了项目你可以运行看到问题:

https://github.com/GuerrillaCoder/OneToManyIssue

数据库添加为 docker 您可以 运行 docker-compose up。希望这显示了我正在尝试做的事情

Npgsql 不支持读取未知数组或记录列类型,例如 array_agg(e.*) 失败:

Unhandled Exception: System.NotSupportedException: The field 'ebooks' has a type currently unknown to Npgsql (OID 347129).

但它确实支持使用 array_agg(e.id) 读取整数数组,您可以改为查询:

var q = @"select b.*, array_agg(e.id) ids from book b
         left join e_book e on e.physical_book_ean = b.ean_number
         group by b.id";

var results = db.SqlList<Dictionary<string,object>>(q);

这将 return 一个 Dictionary Dynamic Result Set,您需要将其组合成一个不同的 ID 集合以查询所有引用的电子书,例如:

//Select All referenced EBooks in a single query  
var allIds = new HashSet<int>();
results.Each(x => (x["ids"] as int[])?.Each(id => allIds.Add(id)));
var ebooks = db.SelectByIds<EBook>(allIds);

然后您可以创建 id => Ebook 的字典映射,并使用它来使用每一行的 ID 填充电子书实体集合:

var ebooksMap = ebooks.ToDictionary(x => x.Id);
results.Each(x => x[nameof(ProductDto.Ebooks)] = (x["ids"] as int[])?
    .Where(id => id != 0).Map(id => ebooksMap[id]) );

然后您可以使用 ServiceStack AutoMapping Utils 将每个对象字典转换为您的产品 DTO:

var dtos = results.Map(x => x.ConvertTo<ProductDto>());