Merge pull request #1130 from hd2y/issues@1113

DynamicFilterCustom 增加支持 Expression 返回值
This commit is contained in:
2881099 2022-07-17 13:02:39 +08:00 committed by GitHub
commit fe65aa0f00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 412 additions and 12 deletions

View File

@ -733,15 +733,6 @@
<param name="modelBuilder"></param> <param name="modelBuilder"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:FreeSqlDbContextExtensions.ApplyConfigurationsFromAssembly(FreeSql.ICodeFirst,System.Reflection.Assembly,System.Func{System.Type,System.Boolean})">
<summary>
根据Assembly扫描所有继承IEntityTypeConfiguration&lt;T&gt;的配置类
</summary>
<param name="codeFirst"></param>
<param name="assembly"></param>
<param name="predicate"></param>
<returns></returns>
</member>
<member name="M:FreeSqlDbContextExtensions.CreateDbContext(IFreeSql)"> <member name="M:FreeSqlDbContextExtensions.CreateDbContext(IFreeSql)">
<summary> <summary>
创建普通数据上下文档对象 创建普通数据上下文档对象

View File

@ -20,6 +20,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="4.0.0" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.18" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View File

@ -1,7 +1,13 @@
using FreeSql.DataAnnotations; using FreeSql.DataAnnotations;
using FreeSql.Internal.Model;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Text.Json.Nodes;
using Xunit; using Xunit;
namespace FreeSql.Tests.Issues namespace FreeSql.Tests.Issues
@ -9,7 +15,7 @@ namespace FreeSql.Tests.Issues
public class _1113 public class _1113
{ {
[Fact] [Fact]
public void PadLeft() public void IncludeLevelTest()
{ {
using (var freeSql = new FreeSqlBuilder() using (var freeSql = new FreeSqlBuilder()
.UseConnectionString(DataType.Sqlite, "Data Source=:memory:;") .UseConnectionString(DataType.Sqlite, "Data Source=:memory:;")
@ -59,9 +65,153 @@ namespace FreeSql.Tests.Issues
// 只能查询到Orgnization // 只能查询到Orgnization
var list2 = freeSql.Select<Order>().IncludeMany(t => t.Orgnization.Company.Departments).ToList(); var list2 = freeSql.Select<Order>().IncludeMany(t => t.Orgnization.Company.Departments).ToList();
//freeSql.Select<Order>().IncludeMany(t => t.OrderItems, then => then.IncludeMany(t => t.Material.Units)).ToList().Dump(); //freeSql.Select<Order>().IncludeMany(t => t.OrderItems, then => then.IncludeMany(t => t.Material.Units)).ToList().Dump();
// 使用扩展方法加载到指定层级
var list3 = freeSql.Select<Order>().IncludeLevel(3).ToList();
} }
} }
[Fact]
public void DynamicLinqTest()
{
using (var freeSql = new FreeSqlBuilder()
.UseConnectionString(DataType.Sqlite, "Data Source=:memory:;")
.UseAutoSyncStructure(true)
.Build())
{
freeSql.Aop.CurdBefore += (s, e) =>
{
Trace.WriteLine(e.Sql);
};
freeSql.Insert(
Enumerable.Range(1, 100)
.Select(i => new Topic { Title = $"new topic {i}", Clicks = 100 })
.ToList())
.ExecuteAffrows();
// 常规用法
var list1 = freeSql.Select<Topic>()
.Where(t => t.Title.StartsWith("new topic 1"))
.ToList();
// 借助 DynamicExpressionParser
var list2 = freeSql.Select<Topic>()
.WhereDynamicLinq("Title.StartsWith(\"new topic 1\")")
.ToList();
// 拓展 DynamicFilter
var dynmaicFilterInfo = new DynamicFilterInfo
{
Field = $"{nameof(DynamicLinqCustom.DynamicLinq)} {typeof(DynamicLinqCustom).FullName},{typeof(DynamicLinqCustom).Assembly.FullName}",
Operator = DynamicFilterOperator.Custom,
Value = "Title.StartsWith(\"new topic 1\")",
};
var list3 = freeSql.Select<Topic>()
.WhereDynamicFilter(dynmaicFilterInfo)
.ToList();
}
}
[Fact]
public void WhereByPropertyTest()
{
using (var freeSql = new FreeSqlBuilder()
.UseConnectionString(DataType.Sqlite, "Data Source=:memory:;")
.UseAutoSyncStructure(true)
.Build())
{
freeSql.Aop.CurdBefore += (s, e) =>
{
Trace.WriteLine(e.Sql);
};
var company = new Company { Id = Guid.NewGuid(), Code = "CO001" };
var department = new Department { Id = Guid.NewGuid(), Code = "D001", CompanyId = company.Id };
var orgnization = new Orgnization { Code = "C001", CompanyId = company.Id };
freeSql.Insert(company).ExecuteAffrows();
freeSql.Insert(orgnization).ExecuteAffrows();
freeSql.Insert(department).ExecuteAffrows();
var materials = new[]
{
new Material{Code="TEST1",Units=new List<Unit>{new Unit{Code = "KG"}}},
new Material{Code="TEST2",Units=new List<Unit>{new Unit{Code = "KG"}}}
};
var repo1 = freeSql.GetGuidRepository<Material>();
repo1.DbContextOptions.EnableCascadeSave = true;
repo1.Insert(materials);
var order = new Order
{
Code = "X001",
OrgnizationId = orgnization.Id,
OrderItems = new List<OrderItem>
{
new OrderItem{ ItemCode = "01", MaterialId = materials[0].Id },
new OrderItem { ItemCode = "02", MaterialId = materials[1].Id },
}
};
var repo2 = freeSql.GetGuidRepository<Order>();
repo2.DbContextOptions.EnableCascadeSave = true;
repo2.Insert(order);
// 根据导航属性过滤数据
//var list1 = freeSql.Select<Order>().Where(t => t.OrderItems.Any(t1 => t1.Material.Units.Any(t2 => t2.Code == "KG"))).ToList();
var filterInfo1 = new DynamicFilterInfo
{
Field = "Code",
Operator = DynamicFilterOperator.Eq,
Value = "KG",
};
var list1 = freeSql.Select<Order>().Where(t => t.OrderItems.Any(t1 => t1.Material.Units.AsSelect().WhereDynamicFilter(filterInfo1).Any())).ToList();
// 导航属性如果是 OneToOne 或者 ManyToOne 默认支持
var filterInfo2 = new DynamicFilterInfo
{
Field = "Orgnization.Company.Code",
Operator = DynamicFilterOperator.Eq,
Value = "CO001",
};
//var list2 = freeSql.Select<Order>().Where(t => t.Orgnization.Company.Code == "CO001").ToList();
var list2 = freeSql.Select<Order>().WhereDynamicFilter(filterInfo2).ToList();
// 实现效果 OrderItems.Material.Units.Code == "KG"
var list3 = freeSql.Select<Order>().Where("OrderItems.Material.Units.Code", DynamicFilterOperator.Eq, "KG").ToList();
// 实现效果 OrderItems.Material.Code == "TEST1"
// Error SQL:
// SELECT a."Id", a."Code", a."OrgnizationId"
// FROM "Order" a
// WHERE (exists(SELECT 1
// FROM "OrderItem" a
// LEFT JOIN "Material" a__Material ON a__Material."Id" = a."MaterialId"
// WHERE (a__Material."Code" = 'TEST1') AND (a."OrderId" = a."Id")
// limit 0,1))
var list4 = freeSql.Select<Order>().Where("OrderItems.Material.Code", DynamicFilterOperator.Eq, "TEST1").ToList();
// 拓展 DynamicFilter
var dynmaicFilterInfo = new DynamicFilterInfo
{
Field = $"{nameof(DynamicLinqCustom.WhereNavigation)} {typeof(DynamicLinqCustom).FullName},{typeof(DynamicLinqCustom).Assembly.FullName}",
Operator = DynamicFilterOperator.Custom,
Value = JsonConvert.SerializeObject(new DynamicFilterInfo { Field = "OrderItems.Material.Units.Code", Operator = DynamicFilterOperator.Eq, Value = "KG" }),
};
var list5 = freeSql.Select<Order>()
.WhereDynamicFilter(dynmaicFilterInfo)
.ToList();
}
}
public class Topic
{
[Column(IsPrimary = true)] public Guid Id { get; set; }
public string Title { get; set; }
public int Clicks { get; set; }
}
public class Order public class Order
{ {
@ -125,4 +275,262 @@ namespace FreeSql.Tests.Issues
} }
public class DynamicLinqCustom
{
[DynamicFilterCustom]
public static LambdaExpression DynamicLinq(object sender, string value)
{
if (string.IsNullOrWhiteSpace(value)) value = "1==2";
ParameterExpression t = Expression.Parameter(sender.GetType().GetGenericArguments()[0], "t");
var exp = DynamicExpressionParser.ParseLambda(new ParameterExpression[] { t }, typeof(bool), value);
return exp;
}
[DynamicFilterCustom]
public static LambdaExpression WhereNavigation(object sender, string value)
{
var filter = Newtonsoft.Json.JsonConvert.DeserializeObject<DynamicFilterInfo>(value);
var method = typeof(FreeSql_1113_Extensions).GetMethods().First(a => a.Name == "GetWhereExpression" && a.GetParameters().Length == 2);
method = method.MakeGenericMethod(sender.GetType().GenericTypeArguments[0]);
var exp = method.Invoke(null, new[] { sender, filter });
return exp as LambdaExpression;
}
}
static class FreeSql_1113_Extensions
{
public static ISelect<T1> Where<T1>(this ISelect<T1> select, string field, DynamicFilterOperator filterOperator, object value)
{
var filter = new DynamicFilterInfo { Field = field, Operator = filterOperator, Value = value };
Expression<Func<T1, bool>> exp = GetWhereExpression(select, filter);
return select.Where(exp);
}
public static Expression<Func<T1, bool>> GetWhereExpression<T1>(ISelect<T1> select, DynamicFilterInfo filter)
{
var properties = filter.Field.Split('.');
var tree = TableRefTree.GetTableRefTree(select, properties.Length, properties);
// 检索
var treeList = GetTreeList(tree).ToList();
var deepest = treeList.Last();
var collectionNodes = treeList.Where(a => a.TableRef.RefType == TableRefType.OneToMany || a.TableRef.RefType == TableRefType.ManyToMany).ToList();
if (deepest.Level != properties.Length)
throw new Exception($"当前类型{typeof(T1)}导航属性{filter.Field}匹配检索失败");
if (collectionNodes.Count == 0)
throw new Exception($"当前类型{typeof(T1)}导航属性{filter.Field}不包含{TableRefType.OneToMany}或者{nameof(TableRefType.ManyToMany)}关系");
var selectMethod = typeof(FreeSqlGlobalExtensions).GetMethods().First(a => a.ContainsGenericParameters && a.GetParameters().Count() == 1);
var parameterExpression = Expression.Parameter(typeof(T1), "t");
var manyLevel = 0;
var exp = GetWhereExpression(select, deepest, filter, properties, null, ref manyLevel);
return exp as Expression<Func<T1, bool>>;
}
private static Expression GetWhereExpression<T1>(ISelect<T1> select, TableRefTree deepest, DynamicFilterInfo filterInfo, string[] properties, Expression body, ref int manyLevel)
{
while (deepest.Parent != null && deepest.TableRef.RefType != TableRefType.ManyToMany && deepest.TableRef.RefType != TableRefType.OneToMany)
{
deepest = deepest.Parent;
}
manyLevel++;
if (manyLevel == 1)
{
filterInfo = new DynamicFilterInfo
{
Field = string.Join(".", properties.Skip(deepest.Level - 1)),
Operator = filterInfo.Operator,
Value = filterInfo.Value,
};
deepest = deepest.Parent;
return GetWhereExpression(select, deepest, filterInfo, properties, body, ref manyLevel);
}
if (body == null)
{
body = Expression.Parameter(deepest.TableInfo.Type, $"t{deepest.Level}");
var sub = deepest;
do
{
sub = sub.Subs.First();
body = Expression.Property(body, sub.TableRef.Property);
} while (sub.TableRef.RefType != TableRefType.ManyToMany && sub.TableRef.RefType != TableRefType.OneToMany);
var selectMethod = typeof(FreeSqlGlobalExtensions).GetMethods().First(a => a.Name == "AsSelect" && a.ContainsGenericParameters && a.GetParameters().Count() == 1).MakeGenericMethod(sub.TableRef.RefEntityType);
body = Expression.Call(null, selectMethod, body);
var asMethod = typeof(ISelect<>).MakeGenericType(sub.TableRef.RefEntityType).GetMethod("As");
var constExpression = Expression.Constant($"t{deepest.Level}");
body = Expression.Call(body, asMethod, constExpression);
var whereDynamicFilterMethod = typeof(ISelect0<,>).MakeGenericType(typeof(ISelect<>).MakeGenericType(sub.TableRef.RefEntityType), sub.TableRef.RefEntityType).GetMethod("WhereDynamicFilter");
body = Expression.Call(body, whereDynamicFilterMethod, Expression.Constant(filterInfo));
var anyMethod = typeof(ISelect0<,>).MakeGenericType(typeof(ISelect<>).MakeGenericType(sub.TableRef.RefEntityType), sub.TableRef.RefEntityType).GetMethod("Any");
body = Expression.Call(body, anyMethod);
deepest = deepest.Parent;
}
else
{
Expression subBody = Expression.Parameter(deepest.TableInfo.Type, $"t{deepest.Level}");
var sub = deepest;
do
{
sub = sub.Subs.First();
subBody = Expression.Property(subBody, sub.TableRef.Property);
} while (sub.TableRef.RefType != TableRefType.ManyToMany && sub.TableRef.RefType != TableRefType.OneToMany);
var selectMethod = typeof(FreeSqlGlobalExtensions).GetMethods().First(a => a.Name == "AsSelect" && a.ContainsGenericParameters && a.GetParameters().Count() == 1).MakeGenericMethod(sub.TableRef.RefEntityType);
subBody = Expression.Call(null, selectMethod, subBody);
var anyMethod = typeof(ISelect<>).MakeGenericType(sub.TableRef.RefEntityType).GetMethod("Any");
var funcType = typeof(Func<,>).MakeGenericType(sub.TableRef.RefEntityType, typeof(bool));
var parameterBody = Expression.Parameter(sub.TableInfo.Type, $"t{sub.Level}");
var lambda = Expression.Lambda(funcType, body, parameterBody);
body = Expression.Call(subBody, anyMethod, lambda);
deepest = deepest.Parent;
}
if (deepest == null)
{
var funcType = typeof(Func<,>).MakeGenericType(typeof(T1), typeof(bool));
var parameterBody = Expression.Parameter(typeof(T1), "t1");
return Expression.Lambda(funcType, body, parameterBody);
}
else
{
return GetWhereExpression(select, deepest, filterInfo, properties, body, ref manyLevel);
}
}
private static IEnumerable<TableRefTree> GetTreeList(TableRefTree tree)
{
if (tree.Subs == null || tree.Subs.Count == 0) yield break;
yield return tree.Subs[0];
foreach (var sub in GetTreeList(tree.Subs[0]))
{
yield return sub;
}
}
public static ISelect<T1> WhereDynamicLinq<T1>(this ISelect<T1> select, string expression)
{
ParameterExpression t = Expression.Parameter(typeof(T1), "t");
var exp = DynamicExpressionParser.ParseLambda(new ParameterExpression[] { t }, typeof(bool), expression);
return select.Where((Expression<Func<T1, bool>>)exp);
}
public static ISelect<T1> IncludeLevel<T1>(this ISelect<T1> select, int level)
{
var tree = TableRefTree.GetTableRefTree(select, level);
return select.IncludeLevel(level, tree);
}
private static ISelect<T1> IncludeLevel<T1>(this ISelect<T1> select, int level, TableRefTree tree, ParameterExpression parameterExpression = null, MemberExpression bodyExpression = null)
{
var includeMethod = select.GetType().GetMethod("Include");
var includeManyMethod = select.GetType().GetMethod("IncludeMany");
parameterExpression ??= Expression.Parameter(tree.TableInfo.Type, "t");
foreach (var sub in tree.Subs)
{
switch (sub.TableRef.RefType)
{
case TableRefType.ManyToOne:
case TableRefType.OneToOne:
{
var body = bodyExpression == null ? Expression.Property(parameterExpression, sub.TableRef.Property) : Expression.Property(bodyExpression, sub.TableRef.Property);
if (sub.Subs.Count == 0)
{
var funcType = typeof(Func<,>).MakeGenericType(parameterExpression.Type, sub.TableRef.RefEntityType);
var navigateSelector = Expression.Lambda(funcType, body, parameterExpression);
includeMethod.MakeGenericMethod(sub.TableRef.RefEntityType).Invoke(select, new object[] { navigateSelector });
}
else
{
select.IncludeLevel(level, sub, parameterExpression, body);
}
}
break;
case TableRefType.ManyToMany:
case TableRefType.OneToMany:
{
var body = bodyExpression == null ? Expression.Property(parameterExpression, sub.TableRef.Property) : Expression.Property(bodyExpression, sub.TableRef.Property);
object then = null;
if (sub.Subs.Count > 0)
{
//var thenSelectType = select.GetType().GetGenericTypeDefinition().MakeGenericType(sub.TableRef.RefEntityType);
var thenSelectType = typeof(ISelect<>).MakeGenericType(sub.TableRef.RefEntityType);
var thenType = typeof(Action<>).MakeGenericType(thenSelectType);
var thenParameter = Expression.Parameter(thenSelectType, "then");
var thenMethod = typeof(FreeSql_1113_Extensions).GetMethod(nameof(IncludeLevel)).MakeGenericMethod(sub.TableRef.RefEntityType);
var thenLevelConst = Expression.Constant(level - sub.Level + 1);
var thenBody = Expression.Call(null, thenMethod, thenParameter, thenLevelConst);
var thenExpression = Expression.Lambda(thenType, thenBody, thenParameter);
then = thenExpression.Compile();
}
var funcType = typeof(Func<,>).MakeGenericType(parameterExpression.Type, typeof(IEnumerable<>).MakeGenericType(sub.TableRef.RefEntityType));
var navigateSelector = Expression.Lambda(funcType, body, parameterExpression);
includeManyMethod.MakeGenericMethod(sub.TableRef.RefEntityType).Invoke(select, new object[] { navigateSelector, then });
}
break;
}
}
return select;
}
static bool CheckRepeat(this TableRefTree tree, List<Type> types = null)
{
if (tree.Parent == null) return false;
if (types == null)
{
types = new List<System.Type> { tree.TableInfo.Type };
return CheckRepeat(tree.Parent, types);
}
return types.Contains(tree.TableInfo.Type) || CheckRepeat(tree.Parent, types);
}
class TableRefTree
{
public int Level { get; set; }
public TableRefTree Parent { get; set; }
public TableInfo TableInfo { get; set; }
public TableRef TableRef { get; set; }
public List<TableRefTree> Subs { get; set; }
public static TableRefTree GetTableRefTree<T1>(ISelect<T1> select, int maxLevel, string[] properties = null)
{
var orm = select.GetType().GetField("_orm").GetValue(select) as IFreeSql;
var tableInfo = orm.CodeFirst.GetTableByEntity(typeof(T1));
var tree = new TableRefTree()
{
Level = 1,
TableInfo = tableInfo,
};
tree.Subs = GetTableRefTree(orm, tree, maxLevel, properties).ToList();
return tree;
}
public static IEnumerable<TableRefTree> GetTableRefTree(IFreeSql orm, TableRefTree tree, int maxLevel, string[] properties = null)
{
if (tree.Level > maxLevel) yield break;
var tableRefs = tree.TableInfo.Properties.Where(property => properties == null || string.Equals(properties[tree.Level - 1], property.Key, StringComparison.OrdinalIgnoreCase)).Select(a => tree.TableInfo.GetTableRef(a.Key, false)).Where(a => a != null).ToList();
foreach (var tableRef in tableRefs)
{
var tableInfo = orm.CodeFirst.GetTableByEntity(tableRef.RefEntityType);
var sub = new TableRefTree()
{
Level = tree.Level + 1,
TableInfo = tableInfo,
TableRef = tableRef,
Parent = tree,
};
// 排除重复类型
if (sub.CheckRepeat()) continue;
sub.Subs = GetTableRefTree(orm, sub, maxLevel, properties).ToList();
yield return sub;
}
}
}
}
} }

View File

@ -832,8 +832,8 @@ namespace FreeSql.Internal.CommonProvider
var fiValue0MethodReturn = fiValue0Method?.Invoke(null, fiValue0Method.GetParameters() var fiValue0MethodReturn = fiValue0Method?.Invoke(null, fiValue0Method.GetParameters()
.Select(a => a.ParameterType == typeof(object) ? (object)this : .Select(a => a.ParameterType == typeof(object) ? (object)this :
(a.ParameterType == typeof(string) ? (object)(fi.Value?.ToString()) : (object)null)) (a.ParameterType == typeof(string) ? (object)(fi.Value?.ToString()) : (object)null))
.ToArray())?.ToString(); .ToArray());
exp = Expression.Call(typeof(SqlExt).GetMethod("InternalRawSql", BindingFlags.NonPublic | BindingFlags.Static), Expression.Constant(fiValue0MethodReturn, typeof(string))); exp = fiValue0MethodReturn is Expression expression ? expression : Expression.Call(typeof(SqlExt).GetMethod("InternalRawSql", BindingFlags.NonPublic | BindingFlags.Static), Expression.Constant(fiValue0MethodReturn?.ToString(), typeof(string)));
break; break;
case DynamicFilterOperator.Contains: case DynamicFilterOperator.Contains: