From e23ae9d7ac688aaa039539b9c671163988b3e845 Mon Sep 17 00:00:00 2001 From: 28810 <28810@YEXIANGQIN> Date: Sat, 16 Mar 2019 22:12:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E9=80=9A=E8=BF=87=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E5=B1=9E=E6=80=A7=20ManyToMany=20=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs | 65 +++++++++--- FreeSql/Internal/CommonExpression.cs | 108 ++++++++++++++++---- 2 files changed, 139 insertions(+), 34 deletions(-) diff --git a/FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs b/FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs index acbd8378..22ef0528 100644 --- a/FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs +++ b/FreeSql.Tests/MySql/Curd/MySqlSelectTest.cs @@ -6,7 +6,7 @@ using System.Linq; using Xunit; namespace FreeSql.Tests.MySql { - + public class MySqlSelectTest { ISelect select => g.mysql.Select(); @@ -79,6 +79,39 @@ namespace FreeSql.Tests.MySql { } + [Fact] + public void AsSelect() { + //OneToOne、ManyToOne + var t0 = g.mysql.Select().Where(a => a.Parent.Parent.Name == "粤语").ToSql(); + //SELECT a.`Id`, a.`Parent_id`, a__Parent.`Id` as3, a__Parent.`Parent_id` as4, a__Parent.`Ddd`, a__Parent.`Name`, a.`Ddd` as7, a.`Name` as8 + //FROM `Tag` a + //LEFT JOIN `Tag` a__Parent ON a__Parent.`Id` = a.`Parent_id` + //LEFT JOIN `Tag` a__Parent__Parent ON a__Parent__Parent.`Id` = a__Parent.`Parent_id` + //WHERE (a__Parent__Parent.`Name` = '粤语') + + //OneToMany + var t1 = g.mysql.Select().Where(a => a.Tags.AsSelect().Any(t => t.Parent.Id == 10)).ToSql(); + //SELECT a.`Id`, a.`Parent_id`, a.`Ddd`, a.`Name` + //FROM `Tag` a + //WHERE (exists(SELECT 1 + // FROM `Tag` t + // LEFT JOIN `Tag` t__Parent ON t__Parent.`Id` = t.`Parent_id` + // WHERE (t__Parent.`Id` = 10) AND (t.`Parent_id` = a.`Id`) + // limit 0,1)) + + //ManyToMany + var t2 = g.mysql.Select().Where(s => s.Tags.AsSelect().Any(t => t.Name == "国语")).ToSql(); + //SELECT a.`Id`, a.`Create_time`, a.`Is_deleted`, a.`Title`, a.`Url` + //FROM `Song` a + //WHERE(exists(SELECT 1 + // FROM `Song_tag` Mt_Ms + // WHERE(Mt_Ms.`Song_id` = a.`Id`) AND(exists(SELECT 1 + // FROM `Tag` t + // WHERE(t.`Name` = '国语') AND(t.`Id` = Mt_Ms.`Tag_id`) + // limit 0, 1)) + // limit 0, 1)) + } + [Fact] public void Lazy() { var tags = g.mysql.Select().Where(a => a.Parent.Name == "xxx") @@ -140,22 +173,22 @@ namespace FreeSql.Tests.MySql { .Where(a => b.Name != "xxx")); var list111sql = list111.ToSql(); var list111data = list111.ToList((a, b, c) => new { + a.Id, + title_substring = a.Title.Substring(0, 1), + a.Type, + ccc = new { a.Id, a.Title }, + tp = a.Type, + tp2 = new { a.Id, - title_substring = a.Title.Substring(0, 1), - a.Type, - ccc = new { a.Id, a.Title }, - tp = a.Type, - tp2 = new { - a.Id, - tp2 = a.Type.Name - }, - tp3 = new { - a.Id, - tp33 = new { - a.Id - } + tp2 = a.Type.Name + }, + tp3 = new { + a.Id, + tp33 = new { + a.Id } - }); + } + }); var ttt122 = g.mysql.Select().Where(a => a.Id > 0).ToSql(); var sql5 = g.mysql.Select().From((s, b, c) => s).Where((a, b, c) => a.Id == b.ParentId).ToSql(); @@ -491,7 +524,7 @@ namespace FreeSql.Tests.MySql { sql = query.ToSql().Replace("\r\n", ""); Assert.Equal("SELECT a.`Id`, a.`Clicks`, a.`TypeGuid`, a.`Title`, a.`CreateTime` FROM `tb_topic` a, `TestTypeInfo` a__Type, `TestTypeParentInfo` a__Type__Parent WHERE (a__Type__Parent.`Name` = 'tparent')", sql); query.ToList(); - + //���û�е������ԣ��򵥶������ query = select.Where((a, b) => b.Guid == a.TypeGuid && b.Name == "typeTitle"); sql = query.ToSql().Replace("\r\n", ""); diff --git a/FreeSql/Internal/CommonExpression.cs b/FreeSql/Internal/CommonExpression.cs index a52e32d9..2fc99f89 100644 --- a/FreeSql/Internal/CommonExpression.cs +++ b/FreeSql/Internal/CommonExpression.cs @@ -205,7 +205,10 @@ namespace FreeSql.Internal { } static ConcurrentDictionary _dicExpressionLambdaToSqlAsSelectMethodInfo = new ConcurrentDictionary(); static ConcurrentDictionary _dicExpressionLambdaToSqlAsSelectWhereMethodInfo = new ConcurrentDictionary(); + static ConcurrentDictionary _dicExpressionLambdaToSqlAsSelectWhereSqlMethodInfo = new ConcurrentDictionary(); + static ConcurrentDictionary _dicExpressionLambdaToSqlAsSelectAnyMethodInfo = new ConcurrentDictionary(); static ConcurrentDictionary _dicNullableValueProperty = new ConcurrentDictionary(); + static ConcurrentDictionary _dicFreeSqlGlobalExtensionsAsSelectExpression = new ConcurrentDictionary(); internal string ExpressionLambdaToSql(Expression exp, List _tables, List _selectColumnMap, Func getSelectGroupingMapString, SelectTableInfoType tbtype, bool isQuoteName) { if (exp == null) return ""; switch (exp.NodeType) { @@ -280,13 +283,14 @@ namespace FreeSql.Internal { if (asSelectBefores.Any()) { asSelectParentExp1 = asSelectBefores.Pop() as MemberExpression; - asSelectParentExp = asSelectBefores.Pop(); - if (asSelectParentExp != null) { - var testExecuteExp = asSelectParentExp; - if (asSelectParentExp.NodeType == ExpressionType.Parameter) //执行leftjoin关联 - testExecuteExp = Expression.Property(testExecuteExp, _common.GetTableByEntity(asSelectParentExp.Type).Properties.First().Value); - asSelectSql = ExpressionLambdaToSql(testExecuteExp, _tables, new List(), getSelectGroupingMapString, SelectTableInfoType.LeftJoin, isQuoteName); - + if (asSelectBefores.Any()) { + asSelectParentExp = asSelectBefores.Pop(); + if (asSelectParentExp != null) { + var testExecuteExp = asSelectParentExp; + if (asSelectParentExp.NodeType == ExpressionType.Parameter) //执行leftjoin关联 + testExecuteExp = Expression.Property(testExecuteExp, _common.GetTableByEntity(asSelectParentExp.Type).Properties.First().Value); + asSelectSql = ExpressionLambdaToSql(testExecuteExp, _tables, new List(), getSelectGroupingMapString, SelectTableInfoType.LeftJoin, isQuoteName); + } } } } @@ -313,22 +317,27 @@ namespace FreeSql.Internal { var parms = method.GetParameters(); var args = new object[call3Exp.Arguments.Count]; for (var a = 0; a < args.Length; a++) { - var argExp = (call3Exp.Arguments[a] as UnaryExpression)?.Operand; - if (argExp != null && argExp.NodeType == ExpressionType.Lambda) { - if (fsqltable1SetAlias == false) { - fsqltables[0].Alias = (argExp as LambdaExpression).Parameters.First().Name; - fsqltable1SetAlias = true; + var arg3Exp = call3Exp.Arguments[a]; + if (arg3Exp.NodeType == ExpressionType.Constant) { + args[a] = (arg3Exp as ConstantExpression)?.Value; + } else { + var argExp = (arg3Exp as UnaryExpression)?.Operand; + if (argExp != null && argExp.NodeType == ExpressionType.Lambda) { + if (fsqltable1SetAlias == false) { + fsqltables[0].Alias = (argExp as LambdaExpression).Parameters.First().Name; + fsqltable1SetAlias = true; + } } + args[a] = argExp; + //if (args[a] == null) ExpressionLambdaToSql(call3Exp.Arguments[a], fsqltables, null, null, SelectTableInfoType.From, true); } - args[a] = argExp; - //if (args[a] == null) ExpressionLambdaToSql(call3Exp.Arguments[a], fsqltables, null, null, SelectTableInfoType.From, true); } method.Invoke(fsql, args); } if (fsql == null) asSelectBefores.Push(exp3tmp); } if (fsql != null) { - if (asSelectEntityType != null) { //执行 asSelect() 的关联 + if (asSelectParentExp != null) { //执行 asSelect() 的关联,OneToMany,ManyToMany var fsqlWhere = _dicExpressionLambdaToSqlAsSelectWhereMethodInfo.GetOrAdd(asSelectEntityType, asSelectEntityType3 => typeof(ISelect<>).MakeGenericType(asSelectEntityType3).GetMethod("Where", new[] { typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(asSelectEntityType3, typeof(bool))) @@ -337,6 +346,70 @@ namespace FreeSql.Internal { var parm123Ref = parm123Tb.GetTableRef(asSelectParentExp1.Member.Name); var fsqlWhereParam = fsqltables.First().Parameter; //Expression.Parameter(asSelectEntityType); Expression fsqlWhereExp = null; + if (parm123Ref.RefType == TableRefType.ManyToMany) { + //g.mysql.Select().Where(a => g.mysql.Select().Where(b => b.Tag_id == a.Id && b.Song_id == 1).Any()); + var manyTb = _common.GetTableByEntity(parm123Ref.RefMiddleEntityType); + var manySubSelectWhere = _dicExpressionLambdaToSqlAsSelectWhereMethodInfo.GetOrAdd(parm123Ref.RefMiddleEntityType, refMiddleEntityType3 => + typeof(ISelect<>).MakeGenericType(refMiddleEntityType3).GetMethod("Where", new[] { + typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(refMiddleEntityType3, typeof(bool))) + })); + var manySubSelectWhereSql = _dicExpressionLambdaToSqlAsSelectWhereSqlMethodInfo.GetOrAdd(parm123Ref.RefMiddleEntityType, refMiddleEntityType3 => + typeof(ISelect0<,>).MakeGenericType(typeof(ISelect<>).MakeGenericType(refMiddleEntityType3), refMiddleEntityType3).GetMethod("Where", new[] { typeof(string), typeof(object) })); + var manySubSelectAny = _dicExpressionLambdaToSqlAsSelectAnyMethodInfo.GetOrAdd(parm123Ref.RefMiddleEntityType, refMiddleEntityType3 => + typeof(ISelect0<,>).MakeGenericType(typeof(ISelect<>).MakeGenericType(refMiddleEntityType3), refMiddleEntityType3).GetMethod("Any", new Type[0])); + var manySubSelectAsSelectExp = _dicFreeSqlGlobalExtensionsAsSelectExpression.GetOrAdd(parm123Ref.RefMiddleEntityType, refMiddleEntityType3 => + Expression.Call( + typeof(FreeSqlGlobalExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(mfil => mfil.Name == "AsSelect" && mfil.GetParameters().Length == 1).FirstOrDefault()?.MakeGenericMethod(refMiddleEntityType3), + Expression.Constant(Activator.CreateInstance(typeof(List<>).MakeGenericType(refMiddleEntityType3))) + )); + var manyMainParam = _tables[0].Parameter; + var manySubSelectWhereParam = Expression.Parameter(parm123Ref.RefMiddleEntityType, $"M{fsqlWhereParam.Name}_M{asSelectParentExp.ToString().Replace(".", "__")}");//, $"{fsqlWhereParam.Name}__"); + Expression manySubSelectWhereExp = null; + for (var mn = 0; mn < parm123Ref.Columns.Count; mn++) { + var col1 = parm123Ref.MiddleColumns[mn]; + var col2 = parm123Ref.Columns[mn]; + var pexp1 = Expression.Property(manySubSelectWhereParam, col1.CsName); + var pexp2 = Expression.Property(asSelectParentExp, col2.CsName); + if (col1.CsType != col2.CsType) { + if (col1.CsType.IsNullableType()) pexp1 = Expression.Property(pexp1, _dicNullableValueProperty.GetOrAdd(col1.CsType, ct1 => ct1.GetProperty("Value"))); + if (col2.CsType.IsNullableType()) pexp2 = Expression.Property(pexp2, _dicNullableValueProperty.GetOrAdd(col2.CsType, ct2 => ct2.GetProperty("Value"))); + } + var tmpExp = Expression.Equal(pexp1, pexp2); + if (mn == 0) manySubSelectWhereExp = tmpExp; + else manySubSelectWhereExp = Expression.And(manySubSelectWhereExp, tmpExp); + } + var manySubSelectExpBoy = Expression.Call( + manySubSelectAsSelectExp, + manySubSelectWhere, + Expression.Lambda( + manySubSelectWhereExp, + manySubSelectWhereParam + ) + ); + Expression fsqlManyWhereExp = null; + for (var mn = 0; mn < parm123Ref.RefColumns.Count; mn++) { + var col1 = parm123Ref.RefColumns[mn]; + var col2 = parm123Ref.MiddleColumns[mn + parm123Ref.Columns.Count + mn]; + var pexp1 = Expression.Property(fsqlWhereParam, col1.CsName); + var pexp2 = Expression.Property(manySubSelectWhereParam, col2.CsName); + if (col1.CsType != col2.CsType) { + if (col1.CsType.IsNullableType()) pexp1 = Expression.Property(pexp1, _dicNullableValueProperty.GetOrAdd(col1.CsType, ct1 => ct1.GetProperty("Value"))); + if (col2.CsType.IsNullableType()) pexp2 = Expression.Property(pexp2, _dicNullableValueProperty.GetOrAdd(col2.CsType, ct2 => ct2.GetProperty("Value"))); + } + var tmpExp = Expression.Equal(pexp1, pexp2); + if (mn == 0) fsqlManyWhereExp = tmpExp; + else fsqlManyWhereExp = Expression.And(fsqlManyWhereExp, tmpExp); + } + fsqltables.Add(new SelectTableInfo { Alias = manySubSelectWhereParam.Name, Parameter = manySubSelectWhereParam, Table = manyTb, Type = SelectTableInfoType.Parent }); + fsqlWhere.Invoke(fsql, new object[] { Expression.Lambda(fsqlManyWhereExp, fsqlWhereParam) }); + var sql2 = fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { "1" })?.ToString(); + if (string.IsNullOrEmpty(sql2) == false) + manySubSelectExpBoy = Expression.Call(manySubSelectExpBoy, manySubSelectWhereSql, Expression.Constant($"exists({sql2.Replace("\r\n", "\r\n\t")})"), Expression.Constant(null)); + manySubSelectExpBoy = Expression.Call(manySubSelectExpBoy, manySubSelectAny); + asSelectBefores.Clear(); + + return ExpressionLambdaToSql(manySubSelectExpBoy, _tables, _selectColumnMap, getSelectGroupingMapString, tbtype, isQuoteName); + } for (var mn = 0; mn < parm123Ref.Columns.Count; mn++) { var col1 = parm123Ref.RefColumns[mn]; var col2 = parm123Ref.Columns[mn]; @@ -354,9 +427,8 @@ namespace FreeSql.Internal { } asSelectBefores.Clear(); var sql = fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { "1" })?.ToString(); - if (string.IsNullOrEmpty(sql) == false) { + if (string.IsNullOrEmpty(sql) == false) return $"exists({sql.Replace("\r\n", "\r\n\t")})"; - } } asSelectBefores.Clear(); } @@ -471,7 +543,7 @@ namespace FreeSql.Internal { find.Parameter = parmExp; if (find == null) { _tables.Add(find = new SelectTableInfo { Table = tbtmp, Alias = alias, On = null, Type = mp == null ? tbtype : SelectTableInfoType.LeftJoin, Parameter = isa ? parmExp : null }); - if (mp?.Expression != null) { //导航条件 + if (mp?.Expression != null) { //导航条件,OneToOne、ManyToOne var firstTb = _tables.First().Table; var parentTb = _common.GetTableByEntity(mp.Expression.Type); var parentTbRef = parentTb.GetTableRef(mp.Member.Name);