diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index 537315e2..26522f10 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -800,5 +800,14 @@ + + + 批量注入 Repository,可以参考代码自行调整 + + + + + + diff --git a/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs index 38f085d6..8ac1e221 100644 --- a/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs @@ -1562,6 +1562,14 @@ WHERE (((cast(a.[Id] as nvarchar(100))) in (SELECT TOP 10 b.[Title] .Where(a => a.Id == tag1.Id || a.Id == tag2.Id) .ToList(); + var tags333 = g.sqlserver.Select() + .IncludeByPropertyName("Tags", + then => then.IncludeByPropertyName("Parent").IncludeByPropertyName("Songs").IncludeByPropertyName("Tags")) + .IncludeByPropertyName("Parent") + .IncludeByPropertyName("Songs") + .Where(a => a.Id == tag1.Id || a.Id == tag2.Id) + .ToList(); + var tags11 = g.sqlserver.Select() .IncludeMany(a => a.Tags.Take(1)) .Include(a => a.Parent) diff --git a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs index a71ff5cb..cfd4b5fc 100644 --- a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs @@ -1368,7 +1368,7 @@ FROM ""TestTypeParentInfo_01"" a", asTableSubSql); .ToList(); by1.IncludeByPropertyName(g.sqlite, "model2.childs", "model2111Idaaa=model2id"); - by1.IncludeByPropertyNameAsync(g.sqlite, "model2.childs", "model2111Idaaa=model2id"); + by1.IncludeByPropertyNameAsync(g.sqlite, "model2.childs", "model2111Idaaa=model2id").Wait(); var t1 = g.sqlite.Select() .IncludeMany(a => a.model2.childs.Where(m3 => m3.model2111Idaaa == a.model2.model2id)) .Where(a => a.id <= model1.id) @@ -1678,6 +1678,12 @@ FROM ""TestTypeParentInfo_01"" a", asTableSubSql); .IncludeMany(a => a.Songs) .Where(a => a.Id == tag1.Id || a.Id == tag2.Id) .ToList(); + var tags3List = g.sqlite.Select() + .Include(a => a.Parent) + .Where(a => a.Id == tag1.Id || a.Id == tag2.Id) + .ToList(); + tags3List.IncludeByPropertyName(g.sqlite, "Tags", then: then => then.IncludeByPropertyName("Parent").IncludeByPropertyName("Songs").IncludeByPropertyName("Tags")); + tags3List.IncludeByPropertyName(g.sqlite, "Songs"); var tags11 = g.sqlite.Select() .IncludeMany(a => a.Tags.Take(1)) diff --git a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs index e964e345..35ae6016 100644 --- a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs +++ b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs @@ -20,6 +20,12 @@ using System.Threading.Tasks; public static partial class FreeSqlGlobalExtensions { +#if net40 +#else + static readonly Lazy _TaskReflectionResultPropertyLazy = new Lazy(() => typeof(Task).GetProperty("Result")); + internal static object GetTaskReflectionResult(this Task task) => _TaskReflectionResultPropertyLazy.Value.GetValue(task, new object[0]); +#endif + #region Type 对象扩展方法 static Lazy> _dicIsNumberType = new Lazy>(() => new Dictionary { @@ -342,26 +348,26 @@ public static partial class FreeSqlGlobalExtensions /// /// /// 选择一个集合或普通属性 - /// 设置临时的子集合关系映射,格式:子类属性=T1属性 + /// 设置临时的子集合关系映射,格式:子类属性=T1属性,多组以逗号分割 /// 设置子集合只取条数 /// 设置子集合只查询部分字段 /// /// - public static List IncludeByPropertyName(this List list, IFreeSql orm, string property, string where = null, int take = 0, string select = null) where T1 : class + public static List IncludeByPropertyName(this List list, IFreeSql orm, string property, string where = null, int take = 0, string select = null, Expression>> then = null) where T1 : class { #if net40 - return IncludeByPropertyNameSyncOrAsync(false, list, orm, property, where, take, select); + return IncludeByPropertyNameSyncOrAsync(false, list, orm, property, where, take, select, then); #else - var task = IncludeByPropertyNameSyncOrAsync(false, list, orm, property, where, take, select); + var task = IncludeByPropertyNameSyncOrAsync(false, list, orm, property, where, take, select, then); if (task.Exception != null) throw task.Exception.InnerException ?? task.Exception; return task.Result; #endif } #if net40 #else - public static Task> IncludeByPropertyNameAsync(this List list, IFreeSql orm, string property, string where = null, int take = 0, string select = null) where T1 : class + public static Task> IncludeByPropertyNameAsync(this List list, IFreeSql orm, string property, string where = null, int take = 0, string select = null, Expression>> then = null) where T1 : class { - return IncludeByPropertyNameSyncOrAsync(true, list, orm, property, where, take, select); + return IncludeByPropertyNameSyncOrAsync(true, list, orm, property, where, take, select, then); } #endif static @@ -370,7 +376,7 @@ public static partial class FreeSqlGlobalExtensions #else async Task> #endif - IncludeByPropertyNameSyncOrAsync(bool isAsync, List list, IFreeSql orm, string property, string where = null, int take = 0, string select = null) where T1 : class + IncludeByPropertyNameSyncOrAsync(bool isAsync, List list, IFreeSql orm, string property, string where, int take, string select, Expression>> then) where T1 : class { if (orm.CodeFirst.IsAutoSyncStructure) { @@ -389,7 +395,7 @@ public static partial class FreeSqlGlobalExtensions { if (props.Length > 1) IncludeByPropertyName(list, orm, string.Join(".", props.Take(props.Length - 1))); - var imsel = IncludeManyByPropertyNameCommonGetSelect(orm, property, where, take, select); + var imsel = IncludeManyByPropertyNameCommonGetSelect(orm, property, where, take, select, then); #if net40 imsel.SetList(list); #else @@ -438,7 +444,7 @@ public static partial class FreeSqlGlobalExtensions }); return list; } - static Select1Provider IncludeManyByPropertyNameCommonGetSelect(IFreeSql orm, string property, string where = null, int take = 0, string select = null) where T1 : class + static Select1Provider IncludeManyByPropertyNameCommonGetSelect(IFreeSql orm, string property, string where, int take, string select, Expression>> then) where T1 : class { if (orm.CodeFirst.IsAutoSyncStructure) { @@ -506,12 +512,20 @@ public static partial class FreeSqlGlobalExtensions memberInitExp = Expression.Lambda(reffuncType, memberInitExp, refparamExp); exp = Expression.Call(refWhereMethod, exp, memberInitExp); } + Delegate newthen = null; + if (then != null) + { + var newthenParm = Expression.Parameter(typeof(ISelect<>).MakeGenericType(reftb.Type)); + var newthenLambdaBody = new Select1Provider.ReplaceIncludeByPropertyNameParameterVisitor().Modify(then, newthenParm); + var newthenLambda = Expression.Lambda(typeof(Action<>).MakeGenericType(newthenParm.Type), newthenLambdaBody, newthenParm); + newthen = newthenLambda.Compile(); + } var funcType = typeof(Func<,>).MakeGenericType(sel._tables[0].Table.Type, typeof(IEnumerable<>).MakeGenericType(reftb.Type)); var navigateSelector = Expression.Lambda(funcType, exp, sel._tables[0].Parameter); var incMethod = sel.GetType().GetMethod("IncludeMany"); if (incMethod == null) throw new Exception(CoreStrings.RunTimeError_Reflection_IncludeMany); - incMethod.MakeGenericMethod(reftb.Type).Invoke(sel, new object[] { navigateSelector, null }); + incMethod.MakeGenericMethod(reftb.Type).Invoke(sel, new object[] { navigateSelector, newthen }); return sel; } #endregion diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 33f2441b..ddb75687 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -5606,7 +5606,7 @@ 即能 ThenInclude,还可以二次过滤(这个 EFCore 做不到?) - + 本方法实现从已知的内存 List 数据,进行和 ISelect.IncludeMany/Include 相同功能的贪婪加载 集合:new List<Song>(new[] { song1, song2, song3 }).IncludeByPropertyName(fsql, "Tags", "ParentId=Id", 5, "Id,Name"); @@ -5618,7 +5618,7 @@ 选择一个集合或普通属性 - 设置临时的子集合关系映射,格式:子类属性=T1属性 + 设置临时的子集合关系映射,格式:子类属性=T1属性,多组以逗号分割 设置子集合只取条数 设置子集合只查询部分字段 diff --git a/FreeSql/Interface/Curd/ISelect/ISelect1.cs b/FreeSql/Interface/Curd/ISelect/ISelect1.cs index 4115a959..ae032f97 100644 --- a/FreeSql/Interface/Curd/ISelect/ISelect1.cs +++ b/FreeSql/Interface/Curd/ISelect/ISelect1.cs @@ -351,6 +351,7 @@ namespace FreeSql /// /// ISelect IncludeByPropertyName(string property); + ISelect IncludeByPropertyName(string property, Expression>> then); /// /// 按属性名字符串进行 Include/IncludeMany 操作 /// @@ -358,6 +359,7 @@ namespace FreeSql /// /// ISelect IncludeByPropertyNameIf(bool condition, string property); + ISelect IncludeByPropertyNameIf(bool condition, string property, Expression>> then); /// /// 实现 select .. from ( select ... from t ) a 这样的功能 diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs index 20dddcd0..fd6c3f31 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs @@ -1441,7 +1441,7 @@ namespace FreeSql.Internal.CommonProvider if (c < ret.Count - 1) continue; await diretTask; - var diret = diretTask.GetType().GetProperty("Result").GetValue(diretTask, new object[0]); + var diret = diretTask.GetTaskReflectionResult(); var otherList = diret as IEnumerable; var retlistidx = 0; foreach (var otherListItem in otherList) diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs index 453d5d6e..16ea5963 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs @@ -471,8 +471,10 @@ namespace FreeSql.Internal.CommonProvider public int InsertInto(string tableName, Expression> select) where TTargetEntity : class => base.InternalInsertInto(tableName, select); - public ISelect IncludeByPropertyNameIf(bool condition, string property) => condition ? IncludeByPropertyName(property) : this; - public ISelect IncludeByPropertyName(string property) + public ISelect IncludeByPropertyNameIf(bool condition, string property) => condition ? IncludeByPropertyName(property, null) : this; + public ISelect IncludeByPropertyNameIf(bool condition, string property, Expression>> then) => condition ? IncludeByPropertyName(property, then) : this; + public ISelect IncludeByPropertyName(string property) => IncludeByPropertyName(property, null); + public ISelect IncludeByPropertyName(string property, Expression>> then) { var exp = ConvertStringPropertyToExpression(property, true); if (exp == null) throw new ArgumentException($"{CoreStrings.Cannot_Resolve_ExpressionTree(nameof(property))}"); @@ -491,7 +493,15 @@ namespace FreeSql.Internal.CommonProvider var navigateSelector = Expression.Lambda(funcType, exp, _tables[0].Parameter); var incMethod = this.GetType().GetMethod("IncludeMany"); if (incMethod == null) throw new Exception(CoreStrings.RunTimeError_Reflection_IncludeMany); - incMethod.MakeGenericMethod(parTbref.RefEntityType).Invoke(this, new object[] { navigateSelector, null }); + Delegate newthen = null; + if (then != null) + { + var newthenParm = Expression.Parameter(typeof(ISelect<>).MakeGenericType(parTbref.RefEntityType)); + var newthenLambdaBody = new ReplaceIncludeByPropertyNameParameterVisitor().Modify(then, newthenParm); + var newthenLambda = Expression.Lambda(typeof(Action<>).MakeGenericType(newthenParm.Type), newthenLambdaBody, newthenParm); + newthen = newthenLambda.Compile(); + } + incMethod.MakeGenericMethod(parTbref.RefEntityType).Invoke(this, new object[] { navigateSelector, newthen }); break; case TableRefType.ManyToOne: case TableRefType.OneToOne: @@ -502,6 +512,35 @@ namespace FreeSql.Internal.CommonProvider } return this; } + public class ReplaceIncludeByPropertyNameParameterVisitor : ExpressionVisitor + { + private Expression _replaceExp; + private ParameterExpression oldParameter; + public Expression Modify(LambdaExpression lambda, Expression replaceExp) + { + this._replaceExp = replaceExp; + this.oldParameter = lambda.Parameters.FirstOrDefault(); + return Visit(lambda.Body); + } + protected override Expression VisitMember(MemberExpression node) + { + if (node.Expression?.NodeType == ExpressionType.Parameter && node.Expression == oldParameter) + return Expression.Property(_replaceExp, node.Member.Name); + return base.VisitMember(node); + } + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (node.Object?.Type == oldParameter.Type) + { + var methodParameterTypes = node.Method.GetParameters().Select(a => a.ParameterType).ToArray(); + var method = _replaceExp.Type.GetMethod(node.Method.Name, methodParameterTypes); + if (node.Object?.NodeType == ExpressionType.Parameter && node.Object == oldParameter) + return Expression.Call(_replaceExp, method, node.Arguments); + return Expression.Call(Visit(node.Object), method, node.Arguments); + } + return base.VisitMethodCall(node); + } + } bool _isIncluded = false; public ISelect IncludeIf(bool condition, Expression> navigateSelector) where TNavigate : class => condition ? Include(navigateSelector) : this;