当前位置: 首页 > 编程语言 > C#

修改IQueryable.Include()的表达式树为Join分享添加条件

时间:2023-04-10 11:42:41 C#

修改IQueryable.Include()的表达式树为Join添加条件Navigation属性也可以过滤所有soft-删除的记录。所以我有一个基本实体,类似于:publicabstractclassEntity{publicintId{get;放;}publicboolIsDeleted{得到;放;}...}和存储库:publicclassBaseStore:IStorewhereTEntity:Entity{protectedreadonlyApplicationDbContextdb;publicIQueryableGetAll(){returndb.Set().Where(e=>!e.IsDeleted).InterceptWith(newInjectConditionVisitor(entity=>!entity.IsDeleted));}publicIQueryableGetAll(Expressionpredicate){returnGetAll().Where(predicate);}publicIQueryableGetAllWithDeleted(){returndb.Set();}...}InterceptWith函数来自这个项目:https://github.com/davidfowl/QueryInterceptor和https://github.com/StefH/QueryInterceptor(与异步实现相同)使用IStore看起来像:varproject=awaitProjectStore.GetAll().Include(p=>p.Versions)。SingleOrDefaultAsync(p=>p.Id==projectId);我实现了一个ExpressionVisitor:internalclassInjectConditionVisitor:ExpressionVisitor{privateExpressionqueryCondition;公共注入条件onVisitor(Expression条件){queryCondition=条件;}publicoverrideExpressionVisit(Expressionnode){returnbase.Visit(node);但这是我卡住的地方我在Visit函数pip中休息一下,看看我得到了什么表达式,当我应该做一些厚颜无耻的事情时,但它永远不会进入Include(p=>p.Versions)的一部分我的树。我看到了一些其他可能有效的解决方案,但这些是“永久性的”,例如EntityFramework.Filters对于大多数用例来说似乎都不错,但是您必须在配置DbContext时添加一个过滤器-但是,您可以禁用过滤器,但我不想为每个查询禁用并重新启用过滤器。另一个类似的解决方案是订阅ObjectContext的ObjectMaterialized事件,但我也不喜欢那样。我的目标是“捕获”访问者中的包含项并修改表达式树以向连接添加另一个条件,如果您使用商店的GetAll函数之一,该条件仅检查记录的IsDeleted字段。任何帮助将不胜感激!更新我的存储库的目的是隐藏基本实体的一些基本行为——它还包含“创建/上次修改者”、“创建/上次修改日期”、时间戳等。我的BLL通过这个存储库获取所有数据,所以它不不用担心,商店会处理一切。还有可能从BaseStore继承特定的类(然后我配置的DI将IStore注入到继承的类中,如果它存在),您可以在其中添加特定的行为。比如你修改了一个item,你需要添加那些修改历史,然后添加到继承store的update函数中。当您查询具有导航属性的类(所以任何类:D)时,问题就开始了。有两个具体实体:publicclassProject:Entity{publicstringName{get;放;}公共字符串描述{得到;放;}公共虚拟ICollection平台{get;放;}//注意:这个版本不是历史数据,只是项目的版本,如:1.0.0、1.4.2、2.1.0等。publicvirtualICollectionVersions{get;放;}}publicclassPlatform:Entity{publicstringName{get;放;}}公共虚拟ICollectionTestFunctions{get;放;}}publicclassProjectVersion:Entity{publicstringCode{get;放;}publicvirtualProjectProject{get;放;因此,如果我想列出项目的版本,我会调用存储:awaitProjectStore.GetAll().Include(p=>p.Versions).SingleOrDefaultAsync(p=>p.Id==projectId)。我不删除项目,但是如果项目存在,它会返回所有相关的版本,包括删除的版本。在这种特定情况下,我可以从另一侧开始并调用ProjectVersionStore,但如果我想查询2+个导航属性,那么它的游戏就结束了:)预期的行为是:如果我在项目中包含该版本,它仅非-应查询已删除的版本-因此生成的sql连接应包含[Versions].[IsDeleted]=FALSE条件。复杂的包括像Include(project=>project.Platforms.Select(platform=>platform.TestFunctions))更复杂。我尝试这样做的原因是我不想将BLL中的所有Include重构为其他内容。这是懒惰的部分:)另一个是我想要一个透明的解决方案,我不想让BLL知道所有这些。如果不是绝对必要,接口应该保持不变。我知道这只是一种扩展方法,但这种行为应该在商店层。您使用的include方法调用QueryableExtensions.Include(source,path1)方法,该方法将表达式转换为字符串路径。这就是include方法的作用:publicstaticIQueryableInclude(thisIQueryablesource,Expression>path){Check.NotNull>(source,"source");Check.NotNull>>(路径,"路径");字符串路径1;如果(!DbHelpers.TryParsePath(path.Body,outpath1)||path1==null)thrownewArgumentException(Strings.DbExtensions_InvalidIncludePathExpression,"path");返回QueryableExtensions.Include(source,path1);因此,您的表达式看起来像这样(检查表达式中的“Include”或“IncludeSpan”方法):value(System.Data.Entity.Core.Objects.ObjectQuery`1[TEntity]).MergeAs(AppendOnly).IncludeSpan(value(System.Data.Entity.Core.Objects.Span))您应该依赖VisitMethodCall来添加您的表达式:internalclassInjectConditionVisitor:ExpressionVisitor{privateExpression>queryCondition;受保护的覆盖表达式VisitMethodCall(MethodCallExpression节点){表达式表达式=节点;if(node.Method.Name=="Include"||node.Method.Name=="IncludeSpan"){//在这里做点什么!让我们添加一个有趣的OrderBy//LAMBDA:x=>x.[PropertyName]varparameter=Expression.Parameter(typeof(T),"x");表达式属性=Expression.Property(参数,"ColumnInt");varlambda=Expression.Lambda(属性,参数);//表达式:表达式.[OrderMethod](x=>x.[PropertyName])varorderByMethod=typeof(Queryable).GetMethods().First(x=>x.Name=="OrderBy"&&x.GetParameters().长度==2);varorderByMethodGeneric=orderByMethod.MakeGenericMethod(typeof(T),property.Type);expression=Expression.Call(null,orderByMethodGeneric,new[]{expression,Expression.Quote(lambda)});}else{expression=base.VisitMethodCall(node);}返回表达式;}}DavidFowl的QueryInterceptor项目不支持“Include”实体框架“Include”实体框架尝试使用反射来查找“include”方法,如果找不到则返回当前查询(在本例中)。免责声明:我是EF+项目的所有者。我添加了一个支持“包含”的QueryInterceptor函数来回答你的问题。由于尚未添加单元测试,此功能尚不可用,但您可以下载并尝试来源:QueryBlockerSource如果您遇到问题,请直接与我联系(电子邮件位于我的GitHub主页底部)或者您我会开始偏离主题。请注意,“Include”方法通过隐藏一些先前的表达式来修改表达式。因此,有时很难理解幕后真正发生的事情。我的项目还包含一个查询过滤器功能,我相信它可以提供更大的灵活性。编辑:从更新的需求中添加了工作示例这是您可以根据自己的要求使用的起始代码:y=>!y.IsDeleted));返回db.Set().Where(e=>!e.IsDeleted).InterceptWith(conditionVisitor);}varproject=awaitProjectStore.GetAll().Include(p=>p.Versions).SingleOrDefaultAsync(p=>p.Id==projectId);内部类InjectConditionVisitor:ExpressionVisitor{privatereadonlystringNavigationString;私有只读IQueryProvider提供者;privatereadonlyFunc,IQueryable>QueryConditionCondition;publicIndition,IQueryProviderprovider,Func,IQueryable>queryCondition){NavigationString=navigationString;提供者=提供者;QueryCondition=queryCondition;}protectedoverrideExpressionVisitMethodCall(MethodCallExpressionnode){Expressionexpressionnode=node;boolisIncludeSpanif.Valid=false(=="IncludeSpan"){varspanValue=(node.Arguments[0]asConstantExpression).Value;//System.Data.Entity.Core.Objects.Span类和SpanList是内部的,让我们玩反射吧!varspanListProperty=spanValue.GetType().GetProperty("SpanList",BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance);varspanList=(IEnumerable)spanListProperty.GetValue(spanValue);foreach(varspaninspanList){varspanNavigationsField=span.GetType().GetField("Navigations",BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance);varspanNavigation=(List)spanNavigationsField.GetValue(span);如果(spanNavigation.Contains(NavigationString)){isIncludeSpanValid=true;休息;}}}if((node.Method.Name=="Include"&&(node.Arguments[0]asConstantExpression).Value.ToString()==NavigationString)||isIncludeSpanValid){//从当前表达式创建一个查询varquery=Provider.CreateQuery(expression);//APPLY查询条件query=查询条件(查询);//更改查询表达式expression=query.Expression;}else{expression=base.VisitMethodCall(node);}返回表达式;据我所知IncludeSpan:当LINQ方法没有修改原始查询时发生Include:当LINQ方法修改原始查询时发生(你不再看到之前的表达式)--Expression:{value(System.Data.Entity.Core.Objects.ObjectQuery`1[Z.Test.EntityFramework.Plus.Association_Multi_OneToMany_Left]).MergeAs(AppendOnly).IncludeSpan(value(System.Data.Entity.Core.Objects.Span))}varq=ctx.Association_Multi_OneToMany_Lefts.Include(x=>x.Right1s).Include(x=>x.Right2s);--表达式:{value(System.Data.Entity.Core.Objects.ObjectQuery`1[Z.Test.EntityFramework.Plus.Association_Multi_OneToMany_Left]).Include("Right2s")}varq=ctx.Association_Multi_OneToMany_Lefts.Include(x=>x.Right1s).Where(x=>x.ColumnInt>10).Include(x=>x.Right2s);如何包含和过滤相关实体包含不允许您过滤相关实体。您可以在这篇文章中找到2个解决方案:EF。如何在模型中只包含一些子结果?以上就是C#学习教程:修改IQueryable.Include()的表达式树,为连接添加条件。如果对大家有用,需要进一步了解C#学习教程,希望大家多加关注——本文来自网络收藏,不代表立场,如涉及侵权,请右击联系管理员删除。如需转载请注明出处: