diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml
index 1ab5bf1c..4335acb5 100644
--- a/FreeSql.DbContext/FreeSql.DbContext.xml
+++ b/FreeSql.DbContext/FreeSql.DbContext.xml
@@ -728,15 +728,6 @@
-
-
- 根据Assembly扫描所有继承IEntityTypeConfiguration<T>的配置类
-
-
-
-
-
-
创建普通数据上下文档对象
@@ -795,14 +786,5 @@
-
-
- 批量注入 Repository,可以参考代码自行调整
-
-
-
-
-
-
diff --git a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj
index 8d8c874a..717e41f2 100644
--- a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj
+++ b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.csproj
@@ -20,6 +20,7 @@
+
all
diff --git a/FreeSql.Tests/FreeSql.Tests/Issues/1113.cs b/FreeSql.Tests/FreeSql.Tests/Issues/1113.cs
index adf30e19..50902789 100644
--- a/FreeSql.Tests/FreeSql.Tests/Issues/1113.cs
+++ b/FreeSql.Tests/FreeSql.Tests/Issues/1113.cs
@@ -1,7 +1,13 @@
using FreeSql.DataAnnotations;
+using FreeSql.Internal.Model;
+using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using System.Linq.Expressions;
+using System.Text.Json.Nodes;
using Xunit;
namespace FreeSql.Tests.Issues
@@ -9,7 +15,7 @@ namespace FreeSql.Tests.Issues
public class _1113
{
[Fact]
- public void PadLeft()
+ public void IncludeLevelTest()
{
using (var freeSql = new FreeSqlBuilder()
.UseConnectionString(DataType.Sqlite, "Data Source=:memory:;")
@@ -59,9 +65,153 @@ namespace FreeSql.Tests.Issues
// 只能查询到Orgnization
var list2 = freeSql.Select().IncludeMany(t => t.Orgnization.Company.Departments).ToList();
//freeSql.Select().IncludeMany(t => t.OrderItems, then => then.IncludeMany(t => t.Material.Units)).ToList().Dump();
+ // 使用扩展方法加载到指定层级
+ var list3 = freeSql.Select().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()
+ .Where(t => t.Title.StartsWith("new topic 1"))
+ .ToList();
+
+ // 借助 DynamicExpressionParser
+ var list2 = freeSql.Select()
+ .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()
+ .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{new Unit{Code = "KG"}}},
+ new Material{Code="TEST2",Units=new List{new Unit{Code = "KG"}}}
+ };
+
+ var repo1 = freeSql.GetGuidRepository();
+ repo1.DbContextOptions.EnableCascadeSave = true;
+ repo1.Insert(materials);
+
+
+ var order = new Order
+ {
+ Code = "X001",
+ OrgnizationId = orgnization.Id,
+ OrderItems = new List
+ {
+ new OrderItem{ ItemCode = "01", MaterialId = materials[0].Id },
+ new OrderItem { ItemCode = "02", MaterialId = materials[1].Id },
+ }
+ };
+
+ var repo2 = freeSql.GetGuidRepository();
+ repo2.DbContextOptions.EnableCascadeSave = true;
+ repo2.Insert(order);
+
+ // 根据导航属性过滤数据
+ //var list1 = freeSql.Select().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().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().Where(t => t.Orgnization.Company.Code == "CO001").ToList();
+ var list2 = freeSql.Select().WhereDynamicFilter(filterInfo2).ToList();
+
+ // 实现效果 OrderItems.Material.Units.Code == "KG"
+ var list3 = freeSql.Select().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().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()
+ .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
{
@@ -125,4 +275,259 @@ 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(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 Where(this ISelect select, string field, DynamicFilterOperator filterOperator, object value)
+ {
+ var filter = new DynamicFilterInfo { Field = field, Operator = filterOperator, Value = value };
+ Expression> exp = GetWhereExpression(select, filter);
+ return select.Where(exp);
+ }
+
+ public static Expression> GetWhereExpression(ISelect 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>;
+ }
+
+ private static Expression GetWhereExpression(ISelect 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 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 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 WhereDynamicLinq(this ISelect select, string expression)
+ {
+ ParameterExpression t = Expression.Parameter(typeof(T1), "t");
+ var exp = DynamicExpressionParser.ParseLambda(new ParameterExpression[] { t }, typeof(bool), expression);
+ return select.Where((Expression>)exp);
+ }
+
+ public static ISelect IncludeLevel(this ISelect select, int level)
+ {
+ var tree = TableRefTree.GetTableRefTree(select, level);
+ return select.IncludeLevel(level, tree);
+ }
+ private static ISelect IncludeLevel(this ISelect 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 types = null)
+ {
+ if (tree.Parent == null) return false;
+ if (types == null)
+ {
+ types = new List { 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 Subs { get; set; }
+
+ public static TableRefTree GetTableRefTree(ISelect 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 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;
+ }
+ }
+ }
+ }
}
diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml
index 995ea24d..1cfa0adb 100644
--- a/FreeSql/FreeSql.xml
+++ b/FreeSql/FreeSql.xml
@@ -4336,6 +4336,7 @@
将对象池设置为不可用,后续 Get/GetAsync 均会报错,同时启动后台定时检查服务恢复可用
+
由【可用】变成【不可用】时返回true,否则返回false
diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs
index 6f7a0fea..c2e93ddc 100644
--- a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs
+++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs
@@ -811,8 +811,8 @@ namespace FreeSql.Internal.CommonProvider
var fiValue0MethodReturn = fiValue0Method?.Invoke(null, fiValue0Method.GetParameters()
.Select(a => a.ParameterType == typeof(object) ? (object)this :
(a.ParameterType == typeof(string) ? (object)(fi.Value?.ToString()) : (object)null))
- .ToArray())?.ToString();
- exp = Expression.Call(typeof(SqlExt).GetMethod("InternalRawSql", BindingFlags.NonPublic | BindingFlags.Static), Expression.Constant(fiValue0MethodReturn, typeof(string)));
+ .ToArray());
+ exp = fiValue0MethodReturn is Expression expression ? expression : Expression.Call(typeof(SqlExt).GetMethod("InternalRawSql", BindingFlags.NonPublic | BindingFlags.Static), Expression.Constant(fiValue0MethodReturn?.ToString(), typeof(string)));
break;
case DynamicFilterOperator.Contains: