From 8a83fea60a65b530b80e9ccab73cb7f613ce0df6 Mon Sep 17 00:00:00 2001 From: 2881099 <2881099@qq.com> Date: Thu, 9 Dec 2021 00:21:20 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=20DynamicFilter=20Custom?= =?UTF-8?q?=20=E8=87=AA=E5=AE=9A=E4=B9=89=E8=A7=A3=E6=9E=90=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/Repository/BaseRepository.cs | 2 +- .../Sqlite/Curd/SqliteSelectTest.cs | 97 ++++++++++++++++--- .../FreeSqlGlobalExpressionCallExtensions.cs | 11 +++ FreeSql/FreeSql.xml | 22 +++++ .../SelectProvider/Select0Provider.cs | 21 +++- FreeSql/Internal/Model/DynamicFilterInfo.cs | 17 +++- 6 files changed, 155 insertions(+), 15 deletions(-) diff --git a/FreeSql.DbContext/Repository/Repository/BaseRepository.cs b/FreeSql.DbContext/Repository/Repository/BaseRepository.cs index 8551c248..1faae1a2 100644 --- a/FreeSql.DbContext/Repository/Repository/BaseRepository.cs +++ b/FreeSql.DbContext/Repository/Repository/BaseRepository.cs @@ -40,7 +40,7 @@ namespace FreeSql { _dbsetPriv?.Dispose(); _dbPriv?.Dispose(); - this.DataFilter.Dispose(); + this.DataFilter?.Dispose(); } finally { diff --git a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs index 85835297..cc17ba19 100644 --- a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs @@ -1,11 +1,13 @@ using FreeSql.DataAnnotations; using FreeSql.Internal.Model; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Text; using Xunit; namespace FreeSql.Tests.Sqlite @@ -1233,7 +1235,7 @@ WHERE (((cast(a.""Id"" as character)) in (SELECT b.""Title"" //---- Select ---- var at0 = g.sqlite.Select() - .IncludeMany(a => a.childs.Where(m3 => m3.model2111Idaaa == a.model2id).Select(m3 => new TestInclude_OneToManyModel3 { id = m3.id })) + .IncludeMany(a => a.childs.Where(m3 => m3.model2111Idaaa == a.model2id).Select(m3 => new TestInclude_OneToManyModel3 { id = m3.id })) .Where(a => a.model2id <= model1.id) .ToList(); var at001 = g.sqlite.Select() @@ -1241,19 +1243,23 @@ WHERE (((cast(a.""Id"" as character)) in (SELECT b.""Title"" .Where(a => a.model2id <= model1.id) .ToList(a => new { - a.model2id, a.childs, childs2 = a.childs + a.model2id, + a.childs, + childs2 = a.childs }); var at1 = g.sqlite.Select() .IncludeMany(a => a.model2.childs.Where(m3 => m3.model2111Idaaa == a.model2.model2id).Select(m3 => new TestInclude_OneToManyModel3 { id = m3.id })) .Where(a => a.id <= model1.id) - .ToList(); + .ToList(); var at111 = g.sqlite.Select() .IncludeMany(a => a.model2.childs.Where(m3 => m3.model2111Idaaa == a.model2.model2id).Select(m3 => new TestInclude_OneToManyModel3 { id = m3.id })) .Where(a => a.id <= model1.id) .ToList(a => new { - a.id, a.model2.childs, childs2 = a.model2.childs + a.id, + a.model2.childs, + childs2 = a.model2.childs }); var at2 = g.sqlite.Select() @@ -1267,7 +1273,9 @@ WHERE (((cast(a.""Id"" as character)) in (SELECT b.""Title"" .Where(a => a.id <= model1.id) .ToList(a => new { - a.id, a.model2.childs, childs2 = a.model2.childs + a.id, + a.model2.childs, + childs2 = a.model2.childs }); var at00 = g.sqlite.Select() @@ -1279,7 +1287,9 @@ WHERE (((cast(a.""Id"" as character)) in (SELECT b.""Title"" .Where(a => a.model2id <= model1.id) .ToList(a => new { - a.model2id, a.childs, childs2 = a.childs + a.model2id, + a.childs, + childs2 = a.childs }); var at11 = g.sqlite.Select() @@ -1290,9 +1300,11 @@ WHERE (((cast(a.""Id"" as character)) in (SELECT b.""Title"" .IncludeMany(a => a.model2.childs.Take(1).Where(m3 => m3.model2111Idaaa == a.model2.model2id).Select(m3 => new TestInclude_OneToManyModel3 { id = m3.id })) .Where(a => a.id <= model1.id) .ToList(a => new - { - a.id, a.model2.childs, childs2 = a.model2.childs - }); + { + a.id, + a.model2.childs, + childs2 = a.model2.childs + }); var at22 = g.sqlite.Select() .IncludeMany(a => a.model2.childs.Take(1).Where(m3 => m3.model2111Idaaa == a.model2.model2id).Select(m3 => new TestInclude_OneToManyModel3 { id = m3.id }), @@ -1305,7 +1317,9 @@ WHERE (((cast(a.""Id"" as character)) in (SELECT b.""Title"" .Where(a => a.id <= model1.id) .ToList(a => new { - a.id, a.model2.childs, childs2 = a.model2.childs + a.id, + a.model2.childs, + childs2 = a.model2.childs }); } @@ -1724,7 +1738,9 @@ WHERE (((cast(a.""Id"" as character)) in (SELECT b.""Title"" .Where(a => a.Id == song1.Id || a.Id == song2.Id || a.Id == song3.Id) .ToList(a => new { - a.Id, a.Is_deleted, a.Tags + a.Id, + a.Is_deleted, + a.Tags }); } @@ -2481,6 +2497,33 @@ ORDER BY a__Parent.""Name""", sql); Assert.Equal(@"SELECT a.""id"", a.""name"", a.""no"", a.""status"" FROM ""ts_dyfilter_enum01"" a WHERE ((not((a.""name"") LIKE '%testname01') OR not((a.""no"") LIKE '%testname01') OR not((a.""id"") LIKE '%testname01') OR a.""status"" = 2))", sql); + + sql = fsql.Select().WhereDynamicFilter(JsonConvert.DeserializeObject(@" +{ + ""Logic"" : ""Or"", + ""Filters"" : + [ + { + ""Field"" : ""MyRawSql FreeSql.Tests.Sqlite.DynamicFilterMyCustom,FreeSql.Tests"", + ""Operator"" : ""Custom"", + ""Value"" : ""(name,no) in (('testname01','testname01'))"" + }, + { + ""Field"" : ""no"", + ""Operator"" : ""NotEndsWith"", + ""Value"" : ""testname01"" + }, + { + ""Field"" : ""MyRawSql FreeSql.Tests.Sqlite.DynamicFilterMyCustom,FreeSql.Tests"", + ""Operator"" : ""Custom"", + ""Value"" : ""(id,status) in (('testname01','finished'))"" + }, + ] +} +")).ToSql(); + Assert.Equal(@"SELECT a.""id"", a.""name"", a.""no"", a.""status"" +FROM ""ts_dyfilter_enum01"" a +WHERE (((name,no) in (('testname01','testname01')) OR not((a.""no"") LIKE '%testname01') OR (id,status) in (('testname01','finished'))))", sql); } class ts_dyfilter_enum01 @@ -2492,4 +2535,36 @@ WHERE ((not((a.""name"") LIKE '%testname01') OR not((a.""no"") LIKE '%testname01 } public enum ts_dyfilter_enum01_status { staring, stoped, finished } } + + public class DynamicFilterMyCustom + { + public static string MyRawSql(string value) => value; + + public static string TupleIn(string value) + { + var sb = new StringBuilder(); + var array = JArray.Parse(value); + var row = 0; + foreach(JObject item in array) + { + if (row++ == 0) + { + sb.Append("("); + var propNames = item.Properties().Select(a => a.Name); + sb.Append(string.Join(", ", propNames)); + sb.Append(") IN ("); + } + if (row == array.Count - 1) + { + sb.Append(")"); + } + if (row > 1) sb.Append(", "); + sb.Append("("); + var propValues = item.Values().Select(a => a?.ToString().Replace("'", "''")); //注入处理 + sb.Append(string.Join(", ", propValues)); + sb.Append(")"); + } + return sb.ToString(); + } + } } diff --git a/FreeSql/Extensions/FreeSqlGlobalExpressionCallExtensions.cs b/FreeSql/Extensions/FreeSqlGlobalExpressionCallExtensions.cs index d456c715..26de1b8e 100644 --- a/FreeSql/Extensions/FreeSqlGlobalExpressionCallExtensions.cs +++ b/FreeSql/Extensions/FreeSqlGlobalExpressionCallExtensions.cs @@ -140,6 +140,17 @@ namespace FreeSql return 0; } + /// + /// 注意:使用者自己承担【注入风险】 + /// + /// + /// + static bool InternalRawSql([RawValue] string sql) + { + expContext.Value.Result = sql; + return false; + } + #region 大小判断 /// /// 大于 > diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 9c849a52..c97a0361 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -1202,6 +1202,13 @@ + + + 注意:使用者自己承担【注入风险】 + + + + 大于 > @@ -4167,6 +4174,21 @@ 此时 Value 的值格式为逗号分割:value1,value2,value3... 或者数组 + + + 自定义解析,此时 Field 为反射信息,Value 为静态方法的参数(string) + 示范:{ Operator: "Custom", Field: "RawSql webapp1.DynamicFilterCustom,webapp1", Value: "(id,name) in ((1,'k'),(2,'m'))" } + 注意:使用者自己承担【注入风险】 + 静态方法定义示范: + namespace webapp1 + { + public class DynamicFilterCustom + { + public static string RawSql(string value) => value; + } + } + + 是否放弃继续读取 diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs index 1c4785a2..a5b2d3e1 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs @@ -544,17 +544,34 @@ namespace FreeSql.Internal.CommonProvider { if (string.IsNullOrEmpty(fi.Field) == false) { - Expression exp = ConvertStringPropertyToExpression(fi.Field); + Expression exp = null; switch (fi.Operator) { + case DynamicFilterOperator.Custom: + var fiValueCustomArray = fi.Field?.ToString().Split(new[] { ' ' }, 2); + if (fiValueCustomArray.Length != 2) throw new ArgumentException("Custom 要求 Field 应该空格分割,并且长度为 2,格式:{静态方法名}{空格}{反射信息}"); + if (string.IsNullOrWhiteSpace(fiValueCustomArray[0])) throw new ArgumentException("Custom {静态方法名}不能为空,格式:{静态方法名}{空格}{反射信息}"); + if (string.IsNullOrWhiteSpace(fiValueCustomArray[1])) throw new ArgumentException("Custom {反射信息}不能为空,格式:{静态方法名}{空格}{反射信息}"); + var fiValue1Type = Type.GetType(fiValueCustomArray[1]); + if (fiValue1Type == null) throw new ArgumentException($"Custom 找到对应的{{反射信息}}:{fiValueCustomArray[1]}"); + var fiValue0Method = fiValue1Type.GetMethod(fiValueCustomArray[0], new Type[] { typeof(string) }); + if (fiValue0Method == null) throw new ArgumentException($"Custom 找到对应的{{静态方法名}}:{fiValueCustomArray[0]}"); + var fiValue0MethodReturn = fiValue0Method?.Invoke(null, new object[] { fi.Value?.ToString() })?.ToString(); + exp = Expression.Call(typeof(SqlExt).GetMethod("InternalRawSql", BindingFlags.NonPublic | BindingFlags.Static), Expression.Constant(fiValue0MethodReturn, typeof(string))); + break; + case DynamicFilterOperator.Contains: case DynamicFilterOperator.StartsWith: case DynamicFilterOperator.EndsWith: case DynamicFilterOperator.NotContains: case DynamicFilterOperator.NotStartsWith: case DynamicFilterOperator.NotEndsWith: + exp = ConvertStringPropertyToExpression(fi.Field); if (exp.Type != typeof(string)) exp = Expression.TypeAs(exp, typeof(string)); break; + default: + exp = ConvertStringPropertyToExpression(fi.Field); + break; } switch (fi.Operator) { @@ -579,7 +596,7 @@ namespace FreeSql.Internal.CommonProvider if (fiValueRangeArray.Length != 2) throw new ArgumentException($"Range 要求 Value 应该逗号分割,并且长度为 2"); exp = Expression.AndAlso( Expression.GreaterThanOrEqual(exp, Expression.Constant(Utils.GetDataReaderValue(exp.Type, fiValueRangeArray[0]), exp.Type)), - Expression.LessThan(exp, Expression.Constant(Utils.GetDataReaderValue(exp.Type, fiValueRangeArray[1]), exp.Type))); + Expression.LessThan(exp, Expression.Constant(Utils.GetDataReaderValue(exp.Type, fiValueRangeArray[1]), exp.Type))); break; case DynamicFilterOperator.DateRange: var fiValueDateRangeArray = getFiListValue(); diff --git a/FreeSql/Internal/Model/DynamicFilterInfo.cs b/FreeSql/Internal/Model/DynamicFilterInfo.cs index 060d772b..df51b953 100644 --- a/FreeSql/Internal/Model/DynamicFilterInfo.cs +++ b/FreeSql/Internal/Model/DynamicFilterInfo.cs @@ -120,6 +120,21 @@ namespace FreeSql.Internal.Model /// not in (1,2,3) /// 此时 Value 的值格式为逗号分割:value1,value2,value3... 或者数组 /// - NotAny + NotAny, + + /// + /// 自定义解析,此时 Field 为反射信息,Value 为静态方法的参数(string) + /// 示范:{ Operator: "Custom", Field: "RawSql webapp1.DynamicFilterCustom,webapp1", Value: "(id,name) in ((1,'k'),(2,'m'))" } + /// 注意:使用者自己承担【注入风险】 + /// 静态方法定义示范: + /// namespace webapp1 + /// { + /// public class DynamicFilterCustom + /// { + /// public static string RawSql(string value) => value; + /// } + /// } + /// + Custom } }