FreeSql icon indicating copy to clipboard operation
FreeSql copied to clipboard

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

Open et2012 opened this issue 1 year ago • 4 comments

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 => ...);

et2012 avatar Jul 09 '24 03:07 et2012

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

2881099 avatar Jul 09 '24 04:07 2881099

这种情况只适合一层级联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;
}

et2012 avatar Jul 09 '24 06:07 et2012

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 });

2881099 avatar Jul 09 '24 11:07 2881099

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 上。

et2012 avatar Jul 09 '24 12:07 et2012