score:29
this is a great question. first of all, here are a couple of workarounds that come to mind (they all require changes to the query):
first workaround
this one maybe a bit obvious and unfortunately not generally applicable: if the selection of items you would need to pass over to enumerable.contains
already exists in a table in the database, you can write a query that calls enumerable.contains
on the corresponding entity set in the predicate instead of bringing the items into memory first. an enumerable.contains
call over data in the database should result in some kind of join-based query that can be cached. e.g. assuming no navigation properties between customers and selectedcustomers, you should be able to write the query like this:
var q = db.customers.where(c =>
db.selectedcustomers.select(s => s.id).contains(c.id));
the syntax of the query with any is a bit simpler in this case:
var q = db.customers.where(c =>
db.selectedcustomers.any(s => s.id == c.id));
if you don't already have the necessary selection data stored in the database, you will probably don't want the overhead of having to store it, so you should consider the next workaround.
second workaround
if you know beforehand that you will have a relatively manageable maximum number of elements in the list you can replace enumerable.contains
with a tree of or-ed equality comparisons, e.g.:
var list = new [] {1,2,3};
var q = db.customers.where(c =>
list[0] == c.id ||
list[1] == c.id ||
list[2] == c.id );
this should produce a parameterized query that can be cached. if the list varies in size from query to query, this should produce a different cache entry for each list size. alternatively you could use a list with a fixed size and pass some sentinel value that you know will never match the value argument, e.g. 0, -1, or alternatively just repeat one of the other values. in order to produce such predicate expression programmatically at runtime based on a list, you might want to consider using something like predicatebuilder.
potential fixes and their challenges
on one hand, changes necessary to support caching of this kind of query using compiledquery explicitly would be pretty complex in the current version of ef. the key reason is that the elements in the ienumerable<t>
passed to the enumerable.contains
method would have to translate into a structural part of the query for the particular translation we produce, e.g.:
var list = new [] {1,2,3};
var q = db.customers.where(c => list.contains(c.id)).tolist();
the enumerable “list” looks like a simple variable in c#/linq but it needs to be translated to a query like this (simplified for clarity):
select * from customers where id in(1,2,3)
if list changes to new [] {5,4,3,2,1}, and we would have to generate the sql query again!
select * from customers where id in(5,4,3,2,1)
as a potential solution, we have talked about leaving generated sql queries open with some kind of special place holder, e.g. store in the query cache that just says
select * from customers where id in(<place holder>)
at execution time, we could pick this sql from the cache and finish the sql generation with the actual values. another option would be to leverage a table-valued parameter for the list if the target database can support it. the first option would probably work ok only with constant values, the latter requires a database that supports a special feature. both are very complex to implement in ef.
auto compiled queries
on the other hand, for automatic compiled queries (as opposed to explicit compiledquery) the issue becomes somewhat artificial: in this case we compute the query cache key after the initial linq translation, hence any ienumerable<t>
argument passed should have already been expanded into dbexpression nodes: a tree of or-ed equality comparisons in ef5, and usually a single dbinexpression node in ef6. since the query tree already contains a distinct expression for each distinct combination of elements in the source argument of enumerable.contains
(and therefore for each distinct output sql query), it is possible to cache the queries.
however even in ef6 these queries are not cached even in the auto compiled queries case. the key reason for that is that we expect the variability of elements in a list to be high (this has to do with the variable size of the list but is also exacerbated by the fact that we normally don't parameterize values that appear as constants to the query, so a list of constants will be translated into constant literals in sql), so with enough calls to a query with enumerable.contains
you could produce considerable cache pollution.
we have considered alternative solutions to this as well, but we haven't implemented any yet. so my conclusion is that you would be better off with the second workaround in most cases if as i said, you know the number of elements in the list will remain small and manageable (otherwise you will face performance issues).
hope this helps!
score:1
this is really a huge problem, and there's no one-size-fits-all answer. however, when most lists are relatively small, diverga's "second workaround" works well. i've built a library distributed as a nuget package to perform this transformation with as little modification to the query as possible:
https://github.com/bchurchill/efcachecontains
it's been tested out in one project, but feedback and user experiences would be appreciated! if any issues come up please report on github so that i can follow-up.
score:2
i had this exact challenge. here is how i tackled this problem for either strings or longs in an extension method for iqueryables.
to limit the caching pollution we create the same query with a multitude n of m (configurable) parameters, so 1 * m, 2 * m etc. so if the setting is 15; the queryplans would have either 15, 30, 45 etc parameters, depending on the number of elements in the contains (we don't know in advance, but probably less than 100) limiting the number of query plans to 3 if the biggest contains is less than or equal to 45.
the remaining parameters are filled with a placeholdervalue that (we know) doesn't exists in the database. in this case '-1'
resulting query part;
... where [filter1].[someproperty] in (@p__linq__0,@p__linq__1, (...) ,@p__linq__19)
... @p__linq__0='somesearchtext1',@p__linq__1='somesearchtext2',@p__linq__2='-1',
(...) ,@p__linq__19='-1'
usage:
icollection<string> searchtexts = .....tolist();
//or
//icollection<long> searchids = .....tolist();
//this is the setting that is relevant for the resulting multitude of possible queryplans
int itemsperset = 15;
iqueryable<myentity> myentities = (from c in dbcontext.myentities
select c)
.wherecontains(d => d.someproperty, searchtexts, "-1", itemsperset);
the extension method:
using system;
using system.collections;
using system.collections.generic;
using system.linq;
using system.linq.expressions;
namespace mycompany.something.extensions
{
public static class iqueryableextensions
{
public static iqueryable<t> wherecontains<t, u>(this iqueryable<t> source, expression<func<t,u>> propertyselector, icollection<u> identifiers, u placeholderthatdoesnotexistsasvalue, int cachelevel)
{
if(!(propertyselector.body is memberexpression))
{
throw new argumentexception("propertyselector must be a memberexpression", nameof(propertyselector));
}
var propertyexpression = propertyselector.body as memberexpression;
var propertyname = propertyexpression.member.name;
return wherecontains(source, propertyname, identifiers, placeholderthatdoesnotexistsasvalue, cachelevel);
}
public static iqueryable<t> wherecontains<t, u>(this iqueryable<t> source, string propertyname, icollection<u> identifiers, u placeholderthatdoesnotexistsasvalue, int cachelevel)
{
return source.where(containspredicatebuilder<t, u>(identifiers, propertyname, placeholderthatdoesnotexistsasvalue, cachelevel));
}
public static expression<func<t, bool>> containspredicatebuilder<t,u>(icollection<u> ids, string propertyname, u placeholdervalue, int cachelevel = 20)
{
if(cachelevel < 1)
{
throw new argumentexception("cachelevel must be greater than or equal to 1", nameof(cachelevel));
}
expression<func<t, bool>> predicate;
var propertyisnullable = nullable.getunderlyingtype(typeof(t).getproperty(propertyname).propertytype) != null;
// fill a list of cachablelevel number of parameters for the property, equal the selected items and padded with the placeholder value to fill the list.
expression finalexpression = expression.constant(false);
var parameter = expression.parameter(typeof(t), "x");
/* factor makes sure that this query part contains a multitude of m parameters (i.e. 20, 40, 60, ...),
* so the number of query plans is limited even if lots of users have more than m items selected */
int factor = math.max(1, (int)math.ceiling((double)ids.count / cachelevel));
for (var i = 0; i < factor * cachelevel; i++)
{
u id = placeholdervalue;
if (i < ids.count)
{
id = ids.elementat(i);
}
var temp = new { id };
var constant = expression.constant(temp);
var field = expression.property(constant, "id");
var member = expression.property(parameter, propertyname);
if (propertyisnullable)
{
member = expression.property(member, "value");
}
var expression = expression.equal(member, field);
finalexpression = expression.orelse(finalexpression, expression);
}
predicate = expression.lambda<func<t, bool>>(finalexpression, parameter);
return predicate;
}
}
}
score:3
as of now, this is still a problem in entity framework core when using the sql server database provider.
i wrote queryablevalues to solve this problem in a flexible and performant way; with it you can compose the values from an ienumerable<t>
in your query, like if it were another entity in your dbcontext
.
in contrast to other solutions out there, queryablevalues achieves this level of performance by:
- resolving with a single round-trip to the database.
- preserving the query's execution plan regardless of the provided values.
usage example:
// sample values.
ienumerable<int> values = enumerable.range(1, 10);
// using a join.
var myquery1 =
from e in dbcontext.myentities
join v in dbcontext.asqueryablevalues(values) on e.id equals v
select new
{
e.id,
e.name
};
// using contains.
var myquery2 =
from e in dbcontext.myentities
where dbcontext.asqueryablevalues(values).contains(e.id)
select new
{
e.id,
e.name
};
you can also compose complex types!
it's available as a nuget package and the project can be found here. it's distributed under the mit license.
the benchmarks speak for themselves.
Source: stackoverflow.com
Related Query
- How to avoid Query Plan re-compilation when using IEnumerable.Contains in Entity Framework LINQ queries?
- How to optimize Entity Framework query when using spatial types and automapper?
- How can I check the number of calls to the database in LINQ query when using .NET Core and Code First?
- How to avoid repeating ToLists when using LINQ and Entity Framework?
- How to avoid a NullReferenceException when using a nested property via projection with Entity Framework Core
- Why does C# compiler create private DisplayClass when using LINQ method Any() and how can I avoid it?
- How to avoid memory overflow when querying large datasets with Entity Framework and LINQ
- How to do a left outer join in Entity Framework without using the query syntax?
- Entity Framework Linq Query to List - Error when using contains: Only primitive types, enumeration types and entity types are supported
- How to reuse a linq expression for 'Where' when using multiple source tables
- How can I view the Entity Framework LINQ query plan cache?
- How to check if XML contains element when using LINQ to XML?
- How can I write the following code more elegantly using LINQ query syntax?
- How to construct IQueryable query using Linq when I just need count without reading all documents in Document-Db database?
- How do I avoid out of memory exceptions when using PLINQ?
- How to query many-to-many relationship with 'AND' condition using LINQ and Entity Framework
- How to use expressions to build a LINQ query dynamically when using an interface to get the column name?
- How to solve LINQ to Entity query duplication when the queries only differ by a property?
- How do I rewrite this Linq query to avoid using FirstOrDefault?
- How to avoid repeating property projections when using EF Core inheritance?
- Exception raised when using a Linq query with Entity Framework
- How can I query using Entity Framework with an alpha-numeric comparison?
- Linq sub query when using a repository pattern with EF code first
- How do I trace a linq query when using PredicateBuilder/AsExpandable?
- How can I get child data when Lazyloading is not enabled using Entity Framework 5?
- How can we express the following code using query expression?
- How to avoid method length creep when using LINQ and vars
- How to get the index of int array when using linq query
- How to query with Contains on a concatenated string in Entity Framework
- How to Query Icollections of Entity Framework Code First Data
More Query from same tag
- How Get All duplicate record using linq
- Fetching orders from database with special kind of grouping / aggregation
- LINQ .Any VS .Exists - What's the difference?
- How to remove specefic rows from linq result
- Building Linq Query Dynamically
- entity framework get entities with child collection with self order
- how to do Max Aggregation in LINQ query syntax?
- ignore accents on search using linq to entities (EF)
- LINQ to SQL displayed in console mode app
- How to combine 2 lists containing csv strings and compare the result with another csv
- LINQ Parent Child Relationship in an Object
- C# SQL Statement transformed TO LINQ how can i translate this statement to a working linq
- How do I insert an element into XML using Linq?
- How to make a Linq sum
- LINQ: Concatenate/Union multiple sequences
- LINQ to DataSet to get generic list from DataTable
- How do I return the record ID on an insert transaction using Linq to SQL
- how do i union int? and int variables in linqpad
- How to express LEFT JOIN when 2 entities are linked by a navigation properties
- Adhoc stored procs on silverlight app
- How to bind data in asp repeater using entity framework?
- How to perform subquery,group by and aggregate together using lambda or LINQ in C#
- Getting the value of a key in an AttributeCollection using LINQ
- Entity Framework: Linq !contains based on table from different database
- C# Linq - Refactoring ForEach Loop with Sub List
- How do I get this Linq query with custom join into a DataSet?
- Joining tables together via junction table in LINQ
- Using Dictionary<T1, T2> in LinQ query
- .Except into a listview
- Dynamic Order By Entity Framework