dotnetcore / FreeSql

🦄 .NET aot orm, C# orm, VB.NET orm, Mysql orm, Postgresql orm, SqlServer orm, Oracle orm, Sqlite orm, Firebird orm, 达梦 orm, 人大金仓 orm, 神通 orm, 翰高 orm, 南大通用 orm, 虚谷 orm, 国产 orm, Clickhouse orm, QuestDB orm, MsAccess orm.
http://freesql.net
MIT License
4.08k stars 849 forks source link

希望导航属性 OneToMany 也支持 LEFT JOIN 而不是查询两次 #1854

Open et2012 opened 2 months ago

et2012 commented 2 months ago

Feature 特性

导航属性 OneToMany 支持 LEFT JOIN,只使用一个 SQL 语句查询完再进行分割,而不是查询两次

简要描述原因

例如主订单 Order LEFT JOIN 多个子订单 SubOrder 的场景,要区分的话算是 OneToMany,但希望只使用一个 SQL 语句查询完再进行分割(而不是查询两次:先查询主表、再查询子表)。

例如 Dapper 可以用 LEFT JOIN 把主表和子表都绑定到 DTO 上,子表记录使用第二个参数进行 map,然后用 System.Linq.GroupBy(主表字段) 选出主表记录:

// 类似 Dapper LEFT JOIN + splitOn
var ordersWithSubOrders = connection.Query<Order, SubOrder, Order>(
        @"SELECT o.*, so.* FROM Orders o
         LEFT JOIN SubOrders so ON o.Id = so.OrderId",
        (order, subOrder) =>
        {
            if (order.SubOrders == null) order.SubOrders = new List<SubOrder>();
            order.SubOrders.Add(subOrder);
            return order;
        },
        splitOn: "OrderId")
        .GroupBy(o => o.Id)
        .Select(g => g.First())
        .ToList();

使用场景

已经在简要描述原因中说明。 这种场景不是 ManyToOne 的原因是:并不是先查询子订单记录,然后通过 Parent 去定位主订单,而是反过来:查询主订单的同时,也返回所有子订单。 当然,这种场景下 LEFT JOIN 返回并拆分的模型只支持 SELECT,不支持级联更新和删除。

另外,我也理解,可以像 Dapper 那样先自行 LEFT JOIN 并输出所有实体,然后自己 GroupBy。

var data = fsql.Select<Topic, Category, CategoryType>()
  .LeftJoin((a,b,c) => a.CategoryId == b.Id)
  .LeftJoin((a,b,c) => b.ParentId == c.Id)
  .Where((a,b,c) => c.Id > 0)
  .ToList((a,b,c) => new { a,b,c });
// data.GroupBy(x => x.a.CategoryId).Select(g => ...);
2881099 commented 2 months ago

这种情况只适合一层级联join,如果多层的情况会导致大量的IO数据冗余。

et2012 commented 2 months ago

这种情况只适合一层级联join,如果多层的情况会导致大量的IO数据冗余。

是,我明白。所以我说的这种情况或许不太算是导航属性,而更像是 Dapper 风格、有点 Geek 的自定义查询。 或者说,在满足某些条件(例如你说的只有一层级联)的前提下,添加相关重载或单独 API,允许 OneToMany 也使用 JOIN。

其实现有的 SelectLeftJoin 也大致够用了,不过 ToList 目前只支持传入一个 Expression(a,b) => new {...}。 是否有可能传入一个 Func<A, B, TOutput> map ,或增加一个 Fluent API,像 Dapper splitOn 那样自己做映射。

既可以做 OneToMany 映射:

(order, subOrder) => {
  order.SubOrders.Add(subOrder);
  return order;
}

也可以做 ManyToOne 映射:

(order, subOrder) => {
  subOrder.Parent = order;
  return subOrder;
}
2881099 commented 2 months ago

fsql.Select<Topic, Category, CategoryType>() .LeftJoin((a,b,c) => a.CategoryId == b.Id) .LeftJoin((a,b,c) => b.ParentId == c.Id) .Where((a,b,c) => c.Id > 0) .ToList((a,b,c) => new { a,b,c });

et2012 commented 2 months ago

fsql.Select<Topic, Category, CategoryType>() .LeftJoin((a,b,c) => a.CategoryId == b.Id) .LeftJoin((a,b,c) => b.ParentId == c.Id) .Where((a,b,c) => c.Id > 0) .ToList((a,b,c) => new { a,b,c });

我知道这种用法,目前也确实是这样用的。 我提这个 Feature Request 的原因前面也说了,有没有可能让 LEFT JOIN 逻辑在特定条件下也能用到 OneToMany 上。