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

LINQtoEntities-多列的where..in子句共享

时间:2023-04-10 20:17:06 C#

LINQtoEntities-where..in多列的子句我正在尝试使用LINQ-to-EF查询以下形式的数据:类位置{字符串国家;字符串城市;字符串地址;…}按元组(国家、城市、地址)查找位置。我尝试了varkeys=new[]{new{Country=...,City=...,Address=...},...}varresult=fromlocinLocationwherekeys.Contains(new{Country=loc.Country,City=loc.City,Address=loc.Address}但LINQ不想接受匿名类型(我理解这是元组在LINQ中表示的方式)作为Contains()的参数。在LINQ中是否有一种“好的”方式来表达这一点有点,同时能够在数据库上运行查询?或者,如果我只是迭代键和Union()-一起编写查询,这对性能是否不利?怎么样:varresult=locations.Where(l=>keys.Any(k=>k.Country==l.Country&&k.City==l.City&&k.Address==l.Address));更新不幸的是,如果您需要在数据库端,EF会抛出NotSupportedException运行查询不符合这个答案。更新2尝试使用自定义类和元组进行各种连接-都没有用。我们谈论的数据量是多少?如果不是太大,您可以进行侧面处理(方便)或使用联合(如果不是更快的话),至少要传输的数据更少)。我的解决方案是构建一个新的扩展方法WhereOr,它使用ExpressionVisitor来构建查询:publicdelegateExpression>Predicat(TClecle);publicstaticclassExtensions{publicstaticIQueryableWhereOr(thisIQueryablesource,IEnumerablecles,Predicatpredicat)whereTCle:ICle,new(){Expression>clause=null;foreach(varpincles){clause=BatisseurFiltre.Or(clause,predicat(p));}returnsource.Where(子句);}}classBatisseurFiltre:ExpressionVisitor{privateParameterExpression_Parametre;privateBatisseurFiltre(ParameterExpressioncle){_Parametre=cle;}protectedoverrideExpressionVisitParameter(ParameterExpressionnode){return_Parametre;}internalstaticExpression>Or(Expression>e1,{Expression>e2)=null;如果(e1==null){表达式=e2;}elseif(e2==null){表达式=e1;}else{varvisiteur=newBatisseurFiltre(e1.Parameters[0]);e2=(表达式>)visiteur.Visit(e2);varbody=Expression.Or(e1.Body,e2.Body);expression=Expression.Lambda>(body,e1.Parameters[0]);}返回表达式;以下生成在数据库上执行的干净的sql代码:varresult=locations.WhereOr(keys,k=>(l=>k.Country==l.Country&&k.City==l.City&&k.地址==l.地址));虽然我无法让@YvesDarmaillac的代码正常工作,但它指向了这个解决方案,您可以在其中构建表达式,并单独添加每个条件。为此,您可以使用UniversalPredicateBuilder(源代码在最后)。这是我的代码://首先我们创建一个表达式。因为我们不能创建一个空的,//我们让它返回false,因为我们将用“或”连接后续的。//以下也可以是:Expression>condition=(x=>false);//但这更清楚。变种条件=PredicateBuilder。创建(x=>假);foreach(varkeyinkeys){//每个返回一个新的表达式condition=condition.Or(x=>x.Country==key.Country&&x.City==key.City&&x.Address==key.地址);}使用(varctx=newMyContext()){varlocations=ctx.Locations.Where(condition);但是需要注意的是过滤列表(本例中的keys变量)不能太大,否则可能会达到参数限制,异常如下:SqlException:IncomingrequestContainstoomanyparameters。服务器最多支持2100个参数。减少参数数量并重新发送请求。因此,在此示例中(每行三个参数),您不能过滤超过700个位置。用两项过滤,在最终的SQL中会生成6个参数。生成的SQL将如下所示(格式化为更清晰):execsp_executesqlN'SELECT[Extent1].[Id]AS[Id],[Extent1].[Country]AS[Country],[Extent1].[City]AS[城市],[Extent1].[地址]AS[地址]FROM[dbo].[位置]AS[Extent1]WHERE((([Extent1].[Country]=@p__linq__0)OR(([Extent1].[Country]ISNULL)AND(@p__linq__0ISNULL)))AND(([Extent1].[City]=@p__linq__1)OR(([Extent1].[City]ISNULL)AND(@p__linq__1ISNULL)))AND(([Extent1].[Address]=@p__linq__2)OR(([Extent1].[Address]ISNULL)AND(@p__linq__2ISNULL))))OR((([Extent1].[Country]=@p__linq__3)OR(([Extent1].[Country]ISNULL)AND(@p__linq__3ISNULL)))AND(([Extent1].[City]=@p__linq__4)OR(([Extent1].[City]ISNULL)AND(@p__linq__4ISNULL)))AND(([Extent1].[Address]=@p__linq__5)OR(([Extent1].[Address]ISNULL)AND(@p__linq__5ISNULL))))',N'@p__linq__0nvarchar(4000),@p__linq__1nvarchar(4000),@p__linq__2nvarchar(4000),@p__linq__3nvarchar(4000),@p__linq__4nvarchar(4000),@p__linq__5nvarchar(4000)',@p__linq__0=N'USA',@p__linq__1=N'NY',@p__linq__2=N'Add1',@p__linq__3=N'UK',@p__linq__4=N'London',@p__linq__5=N'Add2'请注意最初的“false”表达式是如何被正确忽略的,并且没有包含在EntityFramework的最终SQL中最后,这是通用PredicateBuilder的代码,以供记录。//////启用查询谓词的高效、动态组合。///publicstaticclassPredicateBuilder{//////创建一个计算结果为true的谓词。///publicstaticExpression>True(){returnparam=>true;}//////创建一个计算结果为false的谓词。///publicstaticExpression>False(){returnparam=>false;}//////从指定的lambda表达式创建谓词表达式。///publicstaticExpression>Create(Expression>predicate){returnpredicate;}//////使用逻辑“与”将第一个谓词与第二个谓词结合起来。///publicstaticExpression>And(thisExpression>first,Expression>second){returnfirst.Compose(second,Expression.AndAlso);}//////使用逻辑“或”将第一个谓词与第二个谓词组合起来。///publicstaticExpression>Or(thisExpression>first,Expression>second){returnfirst.Compose(second,Expression.OrElse);}//////否定p重新分配。///publicstaticExpression>Not(thisExpression>expression){varnegated=Expression.Not(expression.Body);返回Expression.Lambda>(negated,expression.Parameters);}//////使用指定的合并函数将第一个表达式与第二个表达式合并。///staticExpressionCompose(thisExpressionfirst,Expressionsecond,Funcmerge){//压缩参数(从第二个参数映射到第一个参数)varmap=first.Parameters.Select((f,i)=>new{f,s=second.Parameters[i]}).ToDictionary(p=>ps,p=>pf);//用第一个lambda表达式中的参数替换第二个lambda表达式中的参数varsecondbody=ParameterRebinder.ReplaceParameters(map,second.Body);//使用第一个表达式的参数创建一个合并的lambda表达式returnExpression.Lambda(merge(first.Body,secondBody),first.Parameters);}classParameterRebinder:ExpressionVisitor{只读字典映射;ParameterRebinder(字典narymap){this.map=map??新词典();}publicstaticExpressionReplaceParameters(Dictionarymap,Expressionexp){returnnewParameterRebinder(map).Visit(exp);}protectedoverrideExpressionVisitParameter(ParameterExpressionp){ParameterExpression替换;if(map.TryGetValue(p,outreplacement)){p=replacement;}返回base.VisitParameter(p);}}}varresult=fromlocinLocationwherekeys.Contains(new{Country=l.Country,City=l.City,Address=l.Address}需要:varresult=fromlocinLocationwherekeys.Contains(新的{Country=loc.Country,City=loc.City,Address=loc.Address}selectloc;存在一个EF扩展,其设计与EntityFrameworkCore.MemoryJoin的情况非常相似(名称可能令人困惑,但它同时支持EF6和EFCore)。正如作者文章中所述,它修改了SQL查询,并使用本地列表中的数据注入VALUES构造。并在DB服务器上执行查询。所以对于你的情况,使用可能是这样的varkeys=new[]{new{Country=...,City=...,Address=...},...}//这是重要的部分!varkeysQueryable=context.FromLocalList(keys);varresult=fromlocinLocationjoinkeyinkeysQueryableonnew{loc.Country,loc.City,loc.Address}equalsnew{key.Country,key.City,key.Address}selectloc你试过使用元组类?varkeys=new[]{Tuple.Create("Country","City","Address"),...}varresult=fromlocinLocationwherekeys.Contains(Tuple.Create(loc.Country,loc.City,loc.Address))如果不需要很多组合键,只需在数据中添加LocationKey属性即可。为了避免浪费大量存储空间,这可以用作组合属性的哈希码。然后查询将只在LocationKey上有一个条件。最后,在客户端过滤结果以删除具有相同散列但不同位置的实体。看起来像:classLocation{privatestringcountry;publicstringCountry{get{返回国家;}设置{国家=值;更新位置键();}}私有字符串城市;publicstringCity{get{返回城市;}set{城市=值;更新位置键();}}私有字符串地址;publicstringAddress{get{返回地址;}设置{地址=值;更新位置键();}}privatevoidUpdateLocationKey(){LocationKey=Country.GetHashCode()^City.GetHashCode()^Address.GetHashCode();}intLocationKey;...}然后只需查询LocationKey属性。不理想,但它应该工作。我不认为这对你有用,因为当你在Contains方法中创建一个新对象时,它每次都会创建一个新对象。由于这些对象是匿名的,因此比较它们的方式对应于它们的引用,这对于每个对象都是不同的。另外,看看Jacek的回答。varkeys=new[]{new{Country=...,City=...,Address=...},...}varresult=fromlocinLocationwherekeys.Any(k=>k.Country==loc.Country&&k.City==loc.City&&k.Address==loc.Address)selectloc试试看。我认为正确的方法是varresult=fromlocinLocationwhereloc.Country=_countrywhereloc.City=_citywhereloc.Address=_addressselectloc它看起来没有优化但查询提供程序将转换查询优化时到sql。使用元组或其他类时,查询提供者不知道如何将它们转换为sql以及导致NotSupportedException的原因-编辑-如果您有多个键元组,我认为您必须遍历它们并查询每个元组进行上述查询。同样,这可能看起来并不优雅,但是在单个查询中重新检索所有位置的查询最终可能会花费很长时间:select*fromlocationswhere(locations.Country=@country1andlocations.City=@city1,locations.Adress=@adress1)or(locations.Country=@country2andlocations.City=@city2,locations.Adress=@adress2)or...可能最快的方法是做一个简单的查询,但是让他们作为单个sql脚本,并使用多个结果集来实际获取每个值。我不确定您能否让EF执行此操作。我将Contains替换为IEnumerable的更广泛的Any扩展方法(这是一种特定于列表和数组的方法):varresult=Location.Where(l=>keys.Any(k=>l.Country==k.Country&&l.City=k.City&&l.Address==k.Address);这个也可以写成:对大家有用,需要多了解C#学习教程,希望大家多多关注—varresult=fromlinLocationjoinkinkeysonl.Country==k.Country&&l.City==k.City&&l.Address==k.Addressselectl;本文收集自网络,不代表位置,如涉及侵权,请点击维权联系管理员删除,如需转载请注明出处: