diff --git a/Examples/base_entity/Program.cs b/Examples/base_entity/Program.cs index e800ef21..c1440834 100644 --- a/Examples/base_entity/Program.cs +++ b/Examples/base_entity/Program.cs @@ -1,6 +1,7 @@ using FreeSql; using FreeSql.DataAnnotations; using FreeSql.Extensions; +using FreeSql.Internal.Model; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -87,6 +88,43 @@ namespace base_entity new Products { title = "product-4" }.Save(); new Products { title = "product-5" }.Save(); + Products.Select.WhereDynamicFilter(JsonConvert.DeserializeObject(@" +{ + ""Logic"" : ""Or"", + ""Filters"" : + [ + { + ""Field"" : ""title"", + ""Operator"" : ""eq"", + ""Value"" : ""product-1"", + ""Filters"" : + [ + { + ""Field"" : ""title"", + ""Operator"" : ""contains"", + ""Value"" : ""product-1111"", + } + ] + }, + { + ""Field"" : ""title"", + ""Operator"" : ""eq"", + ""Value"" : ""product-2"" + }, + { + ""Field"" : ""title"", + ""Operator"" : ""eq"", + ""Value"" : ""product-3"" + }, + { + ""Field"" : ""title"", + ""Operator"" : ""eq"", + ""Value"" : ""product-4"" + }, + ] +} +")).ToList(); + var items1 = Products.Select.Limit(10).OrderByDescending(a => a.CreateTime).ToList(); var items2 = fsql.Select().Limit(10).OrderByDescending(a => a.CreateTime).ToList(); diff --git a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs index 8d1b715c..0f011db3 100644 --- a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs @@ -1,4 +1,6 @@ using FreeSql.DataAnnotations; +using FreeSql.Internal.Model; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -1931,5 +1933,105 @@ WHERE (((cast(a.""Id"" as character)) in (SELECT b.""Title"" [Navigate(nameof(ParentCode))] public VM_District_Parent Parent { get; set; } } + + [Fact] + public void WhereDynamicFilter() + { + var fsql = g.sqlite; + var sql = fsql.Select().WhereDynamicFilter(JsonConvert.DeserializeObject(@" +{ + ""Logic"" : ""Or"", + ""Filters"" : + [ + { + ""Field"" : ""Code"", + ""Operator"" : ""Contains"", + ""Value"" : ""val1"", + ""Filters"" : + [ + { + ""Field"" : ""Name"", + ""Operator"" : ""StartsWith"", + ""Value"" : ""val2"", + } + ] + }, + { + ""Field"" : ""Name"", + ""Operator"" : ""EndsWith"", + ""Value"" : ""val3"" + }, + { + ""Field"" : ""ParentCode"", + ""Operator"" : ""Equals"", + ""Value"" : ""val4"" + } + ] +} +")).ToSql(); + Assert.Equal(@"SELECT a.""Code"", a.""Name"", a.""ParentCode"" +FROM ""D_District"" a +WHERE (((a.""Code"") LIKE '%val1%' AND (a.""Name"") LIKE 'val2%' OR (a.""Name"") LIKE '%val3' OR a.""ParentCode"" = 'val4'))", sql); + + sql = fsql.Select().WhereDynamicFilter(JsonConvert.DeserializeObject(@" +{ + ""Logic"" : ""Or"", + ""Filters"" : + [ + { + ""Field"" : ""Code"", + ""Operator"" : ""NotContains"", + ""Value"" : ""val1"", + ""Filters"" : + [ + { + ""Field"" : ""Name"", + ""Operator"" : ""NotStartsWith"", + ""Value"" : ""val2"", + } + ] + }, + { + ""Field"" : ""Name"", + ""Operator"" : ""NotEndsWith"", + ""Value"" : ""val3"" + }, + { + ""Field"" : ""ParentCode"", + ""Operator"" : ""NotEqual"", + ""Value"" : ""val4"" + }, + + { + ""Field"" : ""Parent.Code"", + ""Operator"" : ""eq"", + ""Value"" : ""val11"", + ""Filters"" : + [ + { + ""Field"" : ""Parent.Name"", + ""Operator"" : ""contains"", + ""Value"" : ""val22"", + } + ] + }, + { + ""Field"" : ""Parent.Name"", + ""Operator"" : ""eq"", + ""Value"" : ""val33"" + }, + { + ""Field"" : ""Parent.ParentCode"", + ""Operator"" : ""eq"", + ""Value"" : ""val44"" + } + ] +} +")).ToSql(); + Assert.Equal(@"SELECT a.""Code"", a.""Name"", a.""ParentCode"", a__Parent.""Code"" as4, a__Parent.""Name"" as5, a__Parent.""ParentCode"" as6 +FROM ""D_District"" a +LEFT JOIN ""D_District"" a__Parent ON a__Parent.""Code"" = a.""ParentCode"" +WHERE ((not((a.""Code"") LIKE '%val1%') AND not((a.""Name"") LIKE 'val2%') OR not((a.""Name"") LIKE '%val3') OR a.""ParentCode"" <> 'val4' OR a__Parent.""Code"" = 'val11' AND (a__Parent.""Name"") LIKE '%val22%' OR a__Parent.""Name"" = 'val33' OR a__Parent.""ParentCode"" = 'val44'))", sql); + } } } diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index eb7bd92e..295cad26 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -1276,6 +1276,13 @@ 参数 + + + 动态过滤条件 + + + + 禁用全局过滤功能,不传参数时将禁用所有 @@ -2310,6 +2317,137 @@ + + + 查询,若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 + + + + + + + + + 查询,ExecuteReaderAsync(dr => {}, "select * from user where age > ?age", new { age = 25 }) + + + + + + + 查询 + + + + + + + 查询,ExecuteArrayAsync("select * from user where age > ?age", new { age = 25 }) + + + + + + + + 查询 + + + + + + + 查询,ExecuteDataSetAsync("select * from user where age > ?age; select 2", new { age = 25 }) + + + + + + + + 查询 + + + + + + + 查询,ExecuteDataTableAsync("select * from user where age > ?age", new { age = 25 }) + + + + + + + + 在【主库】执行 + + + + + + + + 在【主库】执行,ExecuteNonQueryAsync("delete from user where age > ?age", new { age = 25 }) + + + + + + + + 在【主库】执行 + + + + + + + + 在【主库】执行,ExecuteScalarAsync("select 1 from user where age > ?age", new { age = 25 }) + + + + + + + + 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > ?age", new SqlParameter { ParameterName = "age", Value = 25 }) + + + + + + + + + + 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > ?age", new { age = 25 }) + + + + + + + + + 执行SQL返回对象集合,Query<User>("select * from user where age > ?age; select * from address", new SqlParameter { ParameterName = "age", Value = 25 }) + + + + + + + + + + 执行SQL返回对象集合,Query<User>("select * from user where age > ?age; select * from address", new { age = 25 }) + + + + + + 可自定义解析表达式 @@ -2793,6 +2931,74 @@ 表达式 + + + 动态过滤条件 + + + + + 属性名:Name + 导航属性:Parent.Name + 多表:b.Name + + + + + 操作符 + + + + + 值 + + + + + Filters 下的逻辑运算符 + + + + + 子过滤条件,它与当前的逻辑关系是 And + 注意:当前 Field 可以留空 + + + + + like + + + + + = + + + + + <> + + + + + > + + + + + >= + + + + + < + + + + + <= + + 中间表,多对多 @@ -2837,6 +3043,12 @@ 超时 + + + 获取资源 + + + 使用完毕后,归还资源 @@ -2907,6 +3119,12 @@ 资源对象 + + + 从对象池获取对象成功的时候触发,通过该方法统计或初始化对象 + + 资源对象 + 归还对象给对象池的时候触发 @@ -3539,167 +3757,4 @@ - - - - - - 使用 or 拼接两个 lambda 表达式 - - - - - - 使用 or 拼接两个 lambda 表达式 - - - true 时生效 - - - - - - 将 lambda 表达式取反 - - - true 时生效 - - - - - 生成类似Mongodb的ObjectId有序、不重复Guid - - - - - - 插入数据 - - - - - - - 插入数据,传入实体 - - - - - - - - 插入数据,传入实体数组 - - - - - - - - 插入数据,传入实体集合 - - - - - - - - 插入数据,传入实体集合 - - - - - - - - 修改数据 - - - - - - - 修改数据,传入动态条件,如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} - - - 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 - - - - - 查询数据 - - - - - - - 查询数据,传入动态条件,如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} - - - 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 - - - - - 删除数据 - - - - - - - 删除数据,传入动态条件,如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} - - - 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 - - - - - 开启事务(不支持异步),60秒未执行完成(可能)被其他线程事务自动提交 - - 事务体 () => {} - - - - 开启事务(不支持异步) - - 超时,未执行完成(可能)被其他线程事务自动提交 - 事务体 () => {} - - - - 开启事务(不支持异步) - - - 事务体 () => {} - 超时,未执行完成(可能)被其他线程事务自动提交 - - - - 数据库访问对象 - - - - - 所有拦截方法都在这里 - - - - - CodeFirst 模式开发相关方法 - - - - - DbFirst 模式开发相关方法 - - - - - 全局过滤设置,可默认附加为 Select/Update/Delete 条件 - - - diff --git a/FreeSql/Interface/Curd/ISelect/ISelect0.cs b/FreeSql/Interface/Curd/ISelect/ISelect0.cs index ca52d7c2..7a5d6713 100644 --- a/FreeSql/Interface/Curd/ISelect/ISelect0.cs +++ b/FreeSql/Interface/Curd/ISelect/ISelect0.cs @@ -1,4 +1,5 @@ -using System; +using FreeSql.Internal.Model; +using System; using System.Collections.Generic; using System.Data; using System.Data.Common; @@ -261,6 +262,13 @@ namespace FreeSql /// TSelect WhereIf(bool condition, string sql, object parms = null); + /// + /// 动态过滤条件 + /// + /// + /// + TSelect WhereDynamicFilter(DynamicFilterInfo filter); + /// /// 禁用全局过滤功能,不传参数时将禁用所有 /// diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs index 9563ae68..42af114d 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs @@ -1025,6 +1025,111 @@ namespace FreeSql.Internal.CommonProvider if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); return this as TSelect; } + + static MethodInfo MethodStringContains = typeof(string).GetMethod("Contains", new[] { typeof(string) }); + static MethodInfo MethodStringStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) }); + static MethodInfo MethodStringEndsWith = typeof(string).GetMethod("EndsWith", new[] { typeof(string) }); + public TSelect WhereDynamicFilter(DynamicFilterInfo filter) + { + if (filter == null) return this as TSelect; + var sb = new StringBuilder(); + ParseFilter(DynamicFilterLogic.And, filter, true); + this.Where(sb.ToString()); + sb.Clear(); + return this as TSelect; + + void ParseFilter(DynamicFilterLogic logic, DynamicFilterInfo fi, bool isend) + { + if (string.IsNullOrEmpty(fi.Field) == false) + { + var field = fi.Field.Split('.').Select(a => a.Trim()).ToArray(); + Expression exp = null; + + if (field.Length == 1) + { + foreach (var tb in _tables) + { + if (tb.Table.ColumnsByCs.TryGetValue(field[0], out var col) && + tb.Table.Properties.TryGetValue(field[0], out var prop)) + { + tb.Parameter = Expression.Parameter(tb.Table.Type, tb.Alias); + exp = Expression.MakeMemberAccess(tb.Parameter, prop); + break; + } + } + if (exp == null) throw new Exception($"无法匹配 {fi.Field}"); + } + else + { + var firstTb = _tables[0]; + var firstTbs = _tables.Where(a => a.AliasInit == field[0]).ToArray(); + if (firstTbs.Length == 1) + { + firstTb = firstTbs[0]; + } + + firstTb.Parameter = Expression.Parameter(firstTb.Table.Type, firstTb.Alias); + var currentType = firstTb.Table.Type; + Expression currentExp = firstTb.Parameter; + + for (var x = 0; x < field.Length; x++) + { + var tmp1 = field[x]; + if (_commonUtils.GetTableByEntity(currentType).Properties.TryGetValue(tmp1, out var prop) == false) + throw new ArgumentException($"{currentType.DisplayCsharp()} 无法找到属性名 {tmp1}"); + currentType = prop.PropertyType; + currentExp = Expression.MakeMemberAccess(currentExp, prop); + } + exp = currentExp; + } + + switch (fi.Operator) + { + case DynamicFilterOperator.Contains: exp = Expression.Call(exp, MethodStringContains, Expression.Constant(fi.Value)); break; + case DynamicFilterOperator.StartsWith: exp = Expression.Call(exp, MethodStringStartsWith, Expression.Constant(fi.Value)); break; + case DynamicFilterOperator.EndsWith: exp = Expression.Call(exp, MethodStringEndsWith, Expression.Constant(fi.Value)); break; + case DynamicFilterOperator.NotContains: exp = Expression.Not(Expression.Call(exp, MethodStringContains, Expression.Constant(fi.Value))); break; + case DynamicFilterOperator.NotStartsWith: exp = Expression.Not(Expression.Call(exp, MethodStringStartsWith, Expression.Constant(fi.Value))); break; + case DynamicFilterOperator.NotEndsWith: exp = Expression.Not(Expression.Call(exp, MethodStringEndsWith, Expression.Constant(fi.Value))); break; + + case DynamicFilterOperator.Equals: + case DynamicFilterOperator.Eq: exp = Expression.Equal(exp, Expression.Constant(Utils.GetDataReaderValue(exp.Type, fi.Value), exp.Type)); break; + case DynamicFilterOperator.NotEqual: exp = Expression.NotEqual(exp, Expression.Constant(Utils.GetDataReaderValue(exp.Type, fi.Value), exp.Type)); break; + + case DynamicFilterOperator.GreaterThan: exp = Expression.GreaterThan(exp, Expression.Constant(Utils.GetDataReaderValue(exp.Type, fi.Value), exp.Type)); break; + case DynamicFilterOperator.GreaterThanOrEqual: exp = Expression.GreaterThanOrEqual(exp, Expression.Constant(Utils.GetDataReaderValue(exp.Type, fi.Value), exp.Type)); break; + case DynamicFilterOperator.LessThan: exp = Expression.LessThan(exp, Expression.Constant(Utils.GetDataReaderValue(exp.Type, fi.Value), exp.Type)); break; + case DynamicFilterOperator.LessThanOrEqual: exp = Expression.LessThanOrEqual(exp, Expression.Constant(Utils.GetDataReaderValue(exp.Type, fi.Value), exp.Type)); break; + } + + var sql = _commonExpression.ExpressionWhereLambda(_tables, exp, null, null, _params); + + sb.Append(sql); + } + if (fi.Filters?.Any() == true) + { + if (string.IsNullOrEmpty(fi.Field) == false) + sb.Append(" AND "); + if (fi.Logic == DynamicFilterLogic.Or) sb.Append("("); + for (var x = 0; x < fi.Filters.Count; x++) + ParseFilter(fi.Logic, fi.Filters[x], x == fi.Filters.Count - 1); + if (fi.Logic == DynamicFilterLogic.Or) sb.Append(")"); + } + + if (isend == false) + { + if (string.IsNullOrEmpty(fi.Field) == false || fi.Filters?.Any() == true) + { + switch (filter.Logic) + { + case DynamicFilterLogic.And: sb.Append(" AND "); break; + case DynamicFilterLogic.Or: sb.Append(" OR "); break; + } + } + } + } + } + public TSelect DisableGlobalFilter(params string[] name) { if (_whereGlobalFilter.Any() == false) return this as TSelect; diff --git a/FreeSql/Internal/Model/DynamicFilterInfo.cs b/FreeSql/Internal/Model/DynamicFilterInfo.cs new file mode 100644 index 00000000..81ffc35e --- /dev/null +++ b/FreeSql/Internal/Model/DynamicFilterInfo.cs @@ -0,0 +1,84 @@ +using FreeSql; +using FreeSql.Internal; +using FreeSql.Internal.CommonProvider; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace FreeSql.Internal.Model +{ + /// + /// 动态过滤条件 + /// + public class DynamicFilterInfo + { + /// + /// 属性名:Name + /// 导航属性:Parent.Name + /// 多表:b.Name + /// + public string Field { get; set; } + /// + /// 操作符 + /// + public DynamicFilterOperator Operator { get; set; } + /// + /// 值 + /// + public string Value { get; set; } + + /// + /// Filters 下的逻辑运算符 + /// + public DynamicFilterLogic Logic { get; set; } + /// + /// 子过滤条件,它与当前的逻辑关系是 And + /// 注意:当前 Field 可以留空 + /// + public List Filters { get; set; } + } + + public enum DynamicFilterLogic { And, Or } + public enum DynamicFilterOperator + { + /// + /// like + /// + Contains, + StartsWith, + EndsWith, + NotContains, + NotStartsWith, + NotEndsWith, + + /// + /// = + /// + Equals, + Eq, + /// + /// <> + /// + NotEqual, + + /// + /// > + /// + GreaterThan, + /// + /// >= + /// + GreaterThanOrEqual, + /// + /// < + /// + LessThan, + /// + /// <= + /// + LessThanOrEqual, + } +}