Troubleshooting LINQ Query Performance in ASP.NET MVC - c#

I'm having trouble with a LINQ query after joining a new table to it. Actually, it returns the data I'm expecting and it runs fast in testing. However, it seems that, as more users connect to the database, the query begins to timeout. For example, everything was working fine for the first 30 or 45 minutes in Production, but then at about 8:20 AM, it started to timeout. Again, I assume this is due to increased usage of the database on the whole.
Here is a little background on the ASP.NET MVC (5) application, in case that helps.
A user submits a referral to our clinic
The referral contains one or more orders
If the person information supplied does not match an existing person, I do several things, including inserting records in an "orders" table (one record for each order selected on the referral).
If the person information supplied does match an existing person in our system, then I "hold" the referral in a queue until it is manually resolved by either matching it to an existing person or by overriding it and creating a new person in the system. At this time, any orders selected in the referral are created in the table.
So, the two main tables to think about in this scenario are the "referral" (named "Referrals" in my code) and "order" (named "ReferralPPAs" in my code) tables. Until now, I have not needed to link the query in question from the Referrals table to the ReferralPPAs table (linking the query to the ReferralPPAs table seems to be what is slowing the query down once database/application usage increases).
Also, in case this helps, the referrals are entered by external users, while the orders I created from the referral are worked in a separate application with internal staff as the users, though it's all in the same database. The ReferralPPAs table is probably being used pretty heavily most of the day.
The query looks like this:
IQueryable<ReferralListViewModel> referrals = (from r in _context.Referrals
join cu in _context.ClinicUsers on r.ClinicId equals cu.ClinicId
/* Here is the seemingly problematic join */
from ppa in _context.ReferralPPAs
.Where(p => p.ref_id == r.seq_no.ToString())
.DefaultIfEmpty()
/* End of seemingly problematic join */
join ec in _context.EnrolledClinics on r.ClinicId equals ec.ClinicId
join pm in _context.ProviderMasters on ec.ClinicId equals pm.ClinicId
join ml in _context.MasterLists on pm.HealthSystemGuid equals ml.Id
join au in _context.Users on r.ApplicationUserId equals au.Id
where cu.UserId == userId
select new ReferralListViewModel()
{
ClinicName = pm.Description,
ClinicId = r.ClinicId,
ReferralId = r.seq_no,
EnteredBy = (au.FirstName ?? string.Empty) + " " + (au.LastName ?? string.Empty),
PatientName = (r.LastName ?? string.Empty) + ", " + (r.FirstName ?? string.Empty),
DateEntered = r.create_timestamp,
Status = ppa != null ? ppa.Status : string.Empty
});
So, without the join I make reference to above, I experience no problems and it runs quite fast. Adding the join also appears to be fast, again, until a certain number of users are on the system (at least that's my assumption).
A couple of other things I've tried to help improve performance and prevent the problem. I set the UseDatabaseNullSemantics to True, which seems to make a big difference in the overall performace.
_context.Configuration.UseDatabaseNullSemantics = true;
I also wondered if the problem was an issue of locking on the table in question, so I tried wrapping the query in a transaction to do a ReadUncommitted.
using (var transaction = _context.Database.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted))
{
//query
}
Again, while this improves the overall performance a little bit, it didn't seem to ultimately resolve the problem.
If anyone has any thoughts, ideas, or suggestions on how to tackle this, I would greatly appreciate it.

Based on the additional information from the comments, looks like the Guid to String conversion in the join condition
p.ref_id == r.seq_no.ToString()
translated to
t1.ref_id = LOWER(CAST(t2.seq_no AS nvarchar(max))))
makes the query not sargable, while the implicit SqlServer conversion
t1.ref_id = t2.seq_no
works just fine.
So the question is how to remove that cast. There is no option for that and also query expression tree does not allow removing it. It would be nice if the SqlServer provider sql generator was doing that optimization, but it doesn't and there is no easy way to hook into it.
As a workaround I can offer the following solution. It uses a custom IDbCommandTreeInterceptor and DbExpressionVisitor to modify the DbCommandTree of the query.
Here is the interception code:
using System;
using System.Data.Entity.Core.Common.CommandTrees;
using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure.Interception;
using System.Linq.Expressions;
using System.Reflection;
namespace EFHacks
{
public class MyDbCommandTreeInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
if (interceptionContext.OriginalResult.DataSpace != DataSpace.SSpace) return;
var queryCommand = interceptionContext.Result as DbQueryCommandTree;
if (queryCommand != null)
{
var newQuery = queryCommand.Query.Accept(new GuidToStringComparisonRewriter());
if (newQuery != queryCommand.Query)
{
interceptionContext.Result = new DbQueryCommandTree(
queryCommand.MetadataWorkspace,
queryCommand.DataSpace,
newQuery);
}
}
}
}
class GuidToStringComparisonRewriter : DefaultExpressionVisitor
{
public override DbExpression Visit(DbComparisonExpression expression)
{
if (IsString(expression.Left.ResultType) && IsString(expression.Right.ResultType))
{
var left = expression.Left;
var right = expression.Right;
if (RemoveCast(ref right) || RemoveCast(ref left))
return CreateComparison(expression.ExpressionKind, left, right);
}
return base.Visit(expression);
}
static bool IsGuid(TypeUsage type)
{
var pt = type.EdmType as PrimitiveType;
return pt != null && pt.PrimitiveTypeKind == PrimitiveTypeKind.Guid;
}
static bool IsString(TypeUsage type)
{
var pt = type.EdmType as PrimitiveType;
return pt != null && pt.PrimitiveTypeKind == PrimitiveTypeKind.String;
}
static bool RemoveCast(ref DbExpression expr)
{
var funcExpr = expr as DbFunctionExpression;
if (funcExpr != null &&
funcExpr.Function.BuiltInTypeKind == BuiltInTypeKind.EdmFunction &&
funcExpr.Function.FullName == "Edm.ToLower" &&
funcExpr.Arguments.Count == 1)
{
var castExpr = funcExpr.Arguments[0] as DbCastExpression;
if (castExpr != null && IsGuid(castExpr.Argument.ResultType))
{
expr = castExpr.Argument;
return true;
}
}
return false;
}
static readonly Func<DbExpressionKind, DbExpression, DbExpression, DbComparisonExpression> CreateComparison = BuildCreateComparisonFunc();
static Func<DbExpressionKind, DbExpression, DbExpression, DbComparisonExpression> BuildCreateComparisonFunc()
{
var kind = Expression.Parameter(typeof(DbExpressionKind), "kind");
var booleanResultType = Expression.Field(null, typeof(DbExpressionBuilder), "_booleanType");
var left = Expression.Parameter(typeof(DbExpression), "left");
var right = Expression.Parameter(typeof(DbExpression), "right");
var result = Expression.New(
typeof(DbComparisonExpression).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null,
new[] { kind.Type, booleanResultType.Type, left.Type, right.Type }, null),
kind, booleanResultType, left, right);
var expr = Expression.Lambda<Func<DbExpressionKind, DbExpression, DbExpression, DbComparisonExpression>>(
result, kind, left, right);
return expr.Compile();
}
}
}
and DbConfiguration to install it:
class MyDbConfiguration : DbConfiguration
{
public MyDbConfiguration()
{
AddInterceptor(new EFHacks.MyDbCommandTreeInterceptor());
}
}
Tested and working in EF6.1.3 and EF6.2 with SqlServer database.
But use it with care.
First, it works only for SqlServer.
Second, it's hackish because I had to use internal field and internal class constructor in order to skip the check for equal types of the comparison operation operands. So some future EF6 update might break it.

Related

passing linq anonymous result to iqueryable object for another query

I have two tables (tbPerson and tbDataLog) where I need to return Id from one table (tbPerson) after checking certain conditions on both. After this, this result should be passed to another query. My first query returns the Id (primary key of a table) successfully and I need to pass these ids to another query so that it return me data based upon these Id. I also has an IQueryable type base object to check certain conditions to fetch data.
IQueryable<tbPerson> dataset
and I cannot changes this from Iqueryable to other as it will break other part of the code)
My first linq statement:
public static IQueryable<LogResults> GetResultsForYes()
{
Databasename ents = new Databasename();
var ids = (from f in ents.tbPerson
join g in ents.tbDataLog
on f.InfoID equals g.RefId
where g.Tag == "subscribed" && g.OldValue == "No" && g.Action == "Modified"
select new LogResults { _LogID = f.Id }).OrderBy(x => x._LogID);
return ids;
}
public class LogResults
{
public int _LogID { get; set; }
}
I access my result something like this where I can see in debugger all the Ids.
IQueryable<LogResults> log = GetResultsForYes();
Problem comes, when I tried to get records from tbPerson based upon these returned Id.
dataset=log.where(x=>x._LogID != 0);
I get this error:
Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Linq.IQueryable'. An explicit conversion exists(are you missing a cast)?
Any suggestions or some other good approach is welcome.
I love this thing about stackoverflow. when we write questions we force our brain to think more deeply and after 30 mins of posting this question, I solved it in a simple way. Sometimes we overcomplicated things!
var ids = (from f in ents.tbPerson
join g in ents.tbDataLog
on f.InfoID equals g.RefId
where g.Tag == "subscribed" && g.OldValue == "No" && g.Action == "Modified"
select new { f.Id }).ToArray();
var allId = ids.Select(x => x.Id).ToArray();
dataset = dataset.Where(x => allId.Contains(x.Id));
#ankit_sharma : I have not tested yours but will give a try and come back to you. Thanks for giving time and effort.
IQueryable<tbPerson> dataset=log.where(x=>x._LogID != 0);
The result of log.where(x=>x._LogID != 0) is an IQueryable<LogResults>, and you are trying to assign this result to dataset of type IQueryable<tbPerson>, two diferent types.
EDIT:
I see you make a join to get the tbPerson ids, and then you do a second query to get the persons. You could get the persons in the first join.
I just modify your code:
IQueryable<tbPerson> persons = from person in ents.tbPerson
join g in ents.tbDataLog
on person.InfoID equals g.RefId
where g.Tag == "subscribed" && g.OldValue == "No" && g.Action == "Modified"
select person;

How to create a custom store expression for my linq queries

Let me first explain what I'm trying to accomplish.
I'm working with a C# ASP.NET MVC 5 project using Entity Framework to communicate with a SQL Server database. Most of the queries utilizes linq for its queries. In various places on the frontend site I'm displaying lists of records and need to provide the means of searching these records via a search bar. The initial idea right now is to allow the user to enter a search phrase with keywords being separated by spaces, and those keywords are used to match any combination of fields in the records of a table.
For example, say my search is "John Doe" against a user table. Consider these being the records in this table:
uFirstName uLastName
---------- ----------
Johnny Doe
John Doe
Jane Doe
The first two records should be returned.
Here's an example method I would call to return the results I expect:
public static List<UserModel> GetUserList(string terms)
{
using (DBConnection dbcontext = new DBConnection())
{
var termlist = (terms == "") ? new List<string>() : terms.Split(' ').ToList();
var linqList = (from u in dbcontext.Users
where
(
(terms == "") ||
(termlist.Any(_s => u.uLastName.Contains(_s))) ||
(termlist.Any(_s => u.uFirstName.Contains(_s)))
)
select new { u.uLastName, u.uFirstName });
return linqList.ToList().ConvertAll<UserModel> ( u => new UserModel { LastName = u.uLastName, FirstName = u.uFirstName } );
}
}
In my project I'm utilizing this search bar in various places being used to search against a variety of tables that obviously have different fields. What I would like to do is create a helper method that allows me to pass in the "terms" string and have it matched against a list of field values within the linq statement generically. Here's an example pseudo method that shows what I would like to change the above method to:
public static List<UserModel> GetUserList(string terms)
{
using (DBConnection dbcontext = new DBConnection())
{
var linqList = (from u in dbcontext.Users
where SearchTermMatch(terms, new List<string>() { u.uLastName, u.uFirstName }) == true
select new { u.uLastName, u.uFirstName });
return linqList.ToList().ConvertAll<UserModel>(u => new UserModel { LastName = u.uLastName, FirstName = u.uFirstName });
}
}
And this is what the helper method would look like:
public static bool SearchTermMatch(string terms, List<string> fieldvalues)
{
if (terms == "") return true;
else
{
var termlist = terms.Split(' ').ToList();
var foundlist = new List<bool>();
foreach (string value in fieldvalues)
foundlist.Add(termlist.Any(s => value.Contains(s)));
return foundlist.Any(f => f == true);
}
}
Even though this compiles fine, at runtime it produces the following error:
LINQ to Entities does not recognize the method 'Boolean SearchTermMatch(System.String, System.Collections.Generic.List`1[System.String])' method, and this method cannot be translated into a store expression.
From all my searching on how to get this working, it's clear I need to utilize Expressions, but I can't for the life of me understand how those work. What I do understand is that Entity Framework wants to convert the linq statements into a query that SQL can understand, and my helper method isn't equipped to do so.
Ultimately what I want to accomplish is to build a helper method that I can later expand upon with more advanced searching techniques. I figure if I start simple with a search on all relevant fields based on a keyword split, I can later add more complexity that I would only have to do to this helper method and all my search bars will benefit from those advancements.
So I guess what I'm looking for is your help on how I can create this helper method that I can use throughout my various linq statements in my project.
Ok, I found a solution to my question. It's not completely ideal, but it gets the job done.
Let me first give reference to the sources I'm using for my solution. I first referred to this answer as the starting point:
https://stackoverflow.com/a/27993416/4566281
This answer referred to a source that I ended up using in my project. If you're using Visual Studio, you can find the package in NuGet, just search for "neinlinq", or get it from this GitHub repository:
https://github.com/axelheer/nein-linq
The only reason I don't consider this my ideal solution is that I was hoping to stick completely to the libraries in .NET / MVC. There's nothing wrong with using a 3rd party library, and in this case, it got the job done for me. But I was hoping to accomplish this as native as possible, and within reason.
So on to my code solution, as I hope this will help someone else in some capacity.
My "helper" function(s) ended up being this (don't forget to include "using NeinLinq;")
[InjectLambda]
public static bool SearchTermMatch(List<string> termlist, List<string> fieldvalues)
{
throw new NotImplementedException();
}
public static Expression<Func<List<string>, List<string>, bool>> SearchTermMatch()
{
return (t,f) =>
(
(t.Count() == 0) ||
(t.Count(_t => f.Any(_f => _f.Contains(_t)) || _t == "") == t.Count())
);
}
And, my linq statement ended up being the following:
public static List<UserModel> GetUserList(string terms)
{
using (DBConnection dbcontext = new DBConnection())
{
var termlist = (terms == "") ? new List<string>() : terms.Split(' ').ToList();
var linqList = (from u in dbcontext.Users
where SearchTermMatch(termlist, new List<string>() { u.uLastName, u.uFirstName })
select new { u.uLastName, u.uFirstName });
return linqList.ToList().ConvertAll<UserModel>(u => new UserModel { LastName = u.uLastName, FirstName = u.uFirstName });
}
}
I also didn't like that I have to construct the "termlist" before the linq statement in order to make the comparisons I wanted. Ideally I'd like to have the "SearchTermMatch" expression to construct the list through something similar to Split so all I had to do was pass in the string "terms", but I couldn't figure out how to accomplish that in the expression. If someone has an idea on how to do that please let me know. I could then have the flexibility to establish my own set of search rules in the expression instead of having the calling linq statement make the list.
So, to come full circle on how this accomplishes my sitution, I can now repurpose SearchTermMatch for all my search bar scenarios. Take for example this statement:
var linqList = (from p in Person
join a in Address on p.AddressID equals a.AddressID
select new { p.ContactName, p.EmailAddress, a.Street, a.City, a.State, a.Zipcode });
I can now easily update it to the following to handle my search bar call:
var termlist = (terms == "") ? new List<string>() : terms.Split(' ').ToList();
var linqList = (from p in Person
join a in Address on p.AddressID equals a.AddressID
where SearchTermMatch(termlist, new List<string>() { p.ContactName, p.EmailAddress, a.Street, a.City, a.State, a.Zipcode })
select new { p.ContactName, p.EmailAddress, a.Street, a.City, a.State, a.Zipcode });

Querying a list of entities with composite keys in EF [duplicate]

given a list of ids, I can query all relevant rows by:
context.Table.Where(q => listOfIds.Contains(q.Id));
But how do you achieve the same functionality when the Table has a composite key?
This is a nasty problem for which I don't know any elegant solution.
Suppose you have these key combinations, and you only want to select the marked ones (*).
Id1 Id2
--- ---
1 2 *
1 3
1 6
2 2 *
2 3 *
... (many more)
How to do this is a way that Entity Framework is happy? Let's look at some possible solutions and see if they're any good.
Solution 1: Join (or Contains) with pairs
The best solution would be to create a list of the pairs you want, for instance Tuples, (List<Tuple<int,int>>) and join the database data with this list:
from entity in db.Table // db is a DbContext
join pair in Tuples on new { entity.Id1, entity.Id2 }
equals new { Id1 = pair.Item1, Id2 = pair.Item2 }
select entity
In LINQ to objects this would be perfect, but, too bad, EF will throw an exception like
Unable to create a constant value of type 'System.Tuple`2 (...) Only primitive types or enumeration types are supported in this context.
which is a rather clumsy way to tell you that it can't translate this statement into SQL, because Tuples is not a list of primitive values (like int or string). For the same reason a similar statement using Contains (or any other LINQ statement) would fail.
Solution 2: In-memory
Of course we could turn the problem into simple LINQ to objects like so:
from entity in db.Table.AsEnumerable() // fetch db.Table into memory first
join pair Tuples on new { entity.Id1, entity.Id2 }
equals new { Id1 = pair.Item1, Id2 = pair.Item2 }
select entity
Needless to say that this is not a good solution. db.Table could contain millions of records.
Solution 3: Two Contains statements (incorrect)
So let's offer EF two lists of primitive values, [1,2] for Id1 and [2,3] for Id2. We don't want to use join, so let's use Contains:
from entity in db.Table
where ids1.Contains(entity.Id1) && ids2.Contains(entity.Id2)
select entity
But now the results also contains entity {1,3}! Well, of course, this entity perfectly matches the two predicates. But let's keep in mind that we're getting closer. In stead of pulling millions of entities into memory, we now only get four of them.
Solution 4: One Contains with computed values
Solution 3 failed because the two separate Contains statements don't only filter the combinations of their values. What if we create a list of combinations first and try to match these combinations? We know from solution 1 that this list should contain primitive values. For instance:
var computed = ids1.Zip(ids2, (i1,i2) => i1 * i2); // [2,6]
and the LINQ statement:
from entity in db.Table
where computed.Contains(entity.Id1 * entity.Id2)
select entity
There are some problems with this approach. First, you'll see that this also returns entity {1,6}. The combination function (a*b) does not produce values that uniquely identify a pair in the database. Now we could create a list of strings like ["Id1=1,Id2=2","Id1=2,Id2=3]" and do
from entity in db.Table
where computed.Contains("Id1=" + entity.Id1 + "," + "Id2=" + entity.Id2)
select entity
(This would work in EF6, not in earlier versions).
This is getting pretty messy. But a more important problem is that this solution is not sargable, which means: it bypasses any database indexes on Id1 and Id2 that could have been used otherwise. This will perform very very poorly.
Solution 5: Best of 2 and 3
So the most viable solution I can think of is a combination of Contains and a join in memory: First do the contains statement as in solution 3. Remember, it got us very close to what we wanted. Then refine the query result by joining the result as an in-memory list:
var rawSelection = from entity in db.Table
where ids1.Contains(entity.Id1) && ids2.Contains(entity.Id2)
select entity;
var refined = from entity in rawSelection.AsEnumerable()
join pair in Tuples on new { entity.Id1, entity.Id2 }
equals new { Id1 = pair.Item1, Id2 = pair.Item2 }
select entity;
It's not elegant, messy all the same maybe, but so far it's the only scalable1 solution to this problem I found, and applied in my own code.
Solution 6: Build a query with OR clauses
Using a Predicate builder like Linqkit or alternatives, you can build a query that contains an OR clause for each element in the list of combinations. This could be a viable option for really short lists. With a couple of hundreds of elements, the query will start performing very poorly. So I don't consider this a good solution unless you can be 100% sure that there will always be a small number of elements. One elaboration of this option can be found here.
Solution 7: Unions
There's also a solution using UNIONs that I posted later here.
1As far as the Contains statement is scalable: Scalable Contains method for LINQ against a SQL backend
Solution for Entity Framework Core with SQL Server
🎉 NEW! QueryableValues EF6 Edition has arrived!
The following solution makes use of QueryableValues. This is a library that I wrote to primarily solve the problem of query plan cache pollution in SQL Server caused by queries that compose local values using the Contains LINQ method. It also allows you to compose values of complex types in your queries in a performant way, which will achieve what's being asked in this question.
First you will need to install and set up the library, after doing that you can use any of the following patterns that will allow you to query your entities using a composite key:
// Required to make the AsQueryableValues method available on the DbContext.
using BlazarTech.QueryableValues;
// Local data that will be used to query by the composite key
// of the fictitious OrderProduct table.
var values = new[]
{
new { OrderId = 1, ProductId = 10 },
new { OrderId = 2, ProductId = 20 },
new { OrderId = 3, ProductId = 30 }
};
// Optional helper variable (needed by the second example due to CS0854)
var queryableValues = dbContext.AsQueryableValues(values);
// Example 1 - Using a Join (preferred).
var example1Results = dbContext
.OrderProduct
.Join(
queryableValues,
e => new { e.OrderId, e.ProductId },
v => new { v.OrderId, v.ProductId },
(e, v) => e
)
.ToList();
// Example 2 - Using Any (similar behavior as Contains).
var example2Results = dbContext
.OrderProduct
.Where(e => queryableValues
.Where(v =>
v.OrderId == e.OrderId &&
v.ProductId == e.ProductId
)
.Any()
)
.ToList();
Useful Links
Nuget Package
GitHub Repository
Benchmarks
QueryableValues is distributed under the MIT license.
You can use Union for each composite primary key:
var compositeKeys = new List<CK>
{
new CK { id1 = 1, id2 = 2 },
new CK { id1 = 1, id2 = 3 },
new CK { id1 = 2, id2 = 4 }
};
IQuerable<CK> query = null;
foreach(var ck in compositeKeys)
{
var temp = context.Table.Where(x => x.id1 == ck.id1 && x.id2 == ck.id2);
query = query == null ? temp : query.Union(temp);
}
var result = query.ToList();
You can create a collection of strings with both keys like this (I am assuming that your keys are int type):
var id1id2Strings = listOfIds.Select(p => p.Id1+ "-" + p.Id2);
Then you can just use "Contains" on your db:
using (dbEntities context = new dbEntities())
{
var rec = await context.Table1.Where(entity => id1id2Strings .Contains(entity.Id1+ "-" + entity.Id2));
return rec.ToList();
}
You need a set of objects representing the keys you want to query.
class Key
{
int Id1 {get;set;}
int Id2 {get;set;}
If you have two lists and you simply check that each value appears in their respective list then you are getting the cartesian product of the lists - which is likely not what you want. Instead you need to query the specific combinations required
List<Key> keys = // get keys;
context.Table.Where(q => keys.Any(k => k.Id1 == q.Id1 && k.Id2 == q.Id2));
I'm not completely sure that this is valid use of Entity Framework; you may have issues with sending the Key type to the database. If that happens then you can be creative:
var composites = keys.Select(k => p1 * k.Id1 + p2 * k.Id2).ToList();
context.Table.Where(q => composites.Contains(p1 * q.Id1 + p2 * q.Id2));
You can create an isomorphic function (prime numbers are good for this), something like a hashcode, which you can use to compare the pair of values. As long as the multiplicative factors are co-prime this pattern will be isomorphic (one-to-one) - i.e. the result of p1*Id1 + p2*Id2 will uniquely identify the values of Id1 and Id2 as long as the prime numbers are correctly chosen.
But then you end up in a situation where you're implementing complex concepts and someone is going to have to support this. Probably better to write a stored procedure which takes the valid key objects.
Ran into this issue as well and needed a solution that both did not perform a table scan and also provided exact matches.
This can be achieved by combining Solution 3 and Solution 4 from Gert Arnold's Answer
var firstIds = results.Select(r => r.FirstId);
var secondIds = results.Select(r => r.SecondId);
var compositeIds = results.Select(r => $"{r.FirstId}:{r.SecondId}");
var query = from e in dbContext.Table
//first check the indexes to avoid a table scan
where firstIds.Contains(e.FirstId) && secondIds.Contains(e.SecondId))
//then compare the compositeId for an exact match
//ToString() must be called unless using EF Core 5+
where compositeIds.Contains(e.FirstId.ToString() + ":" + e.SecondId.ToString()))
select e;
var entities = await query.ToListAsync();
For EF Core I use a slightly modified version of the bucketized IN method by EricEJ to map composite keys as tuples. It performs pretty well for small sets of data.
Sample usage
List<(int Id, int Id2)> listOfIds = ...
context.Table.In(listOfIds, q => q.Id, q => q.Id2);
Implementation
public static IQueryable<TQuery> In<TKey1, TKey2, TQuery>(
this IQueryable<TQuery> queryable,
IEnumerable<(TKey1, TKey2)> values,
Expression<Func<TQuery, TKey1>> key1Selector,
Expression<Func<TQuery, TKey2>> key2Selector)
{
if (values is null)
{
throw new ArgumentNullException(nameof(values));
}
if (key1Selector is null)
{
throw new ArgumentNullException(nameof(key1Selector));
}
if (key2Selector is null)
{
throw new ArgumentNullException(nameof(key2Selector));
}
if (!values.Any())
{
return queryable.Take(0);
}
var distinctValues = Bucketize(values);
if (distinctValues.Length > 1024)
{
throw new ArgumentException("Too many parameters for SQL Server, reduce the number of parameters", nameof(values));
}
var predicates = distinctValues
.Select(v =>
{
// Create an expression that captures the variable so EF can turn this into a parameterized SQL query
Expression<Func<TKey1>> value1AsExpression = () => v.Item1;
Expression<Func<TKey2>> value2AsExpression = () => v.Item2;
var firstEqual = Expression.Equal(key1Selector.Body, value1AsExpression.Body);
var visitor = new ReplaceParameterVisitor(key2Selector.Parameters[0], key1Selector.Parameters[0]);
var secondEqual = Expression.Equal(visitor.Visit(key2Selector.Body), value2AsExpression.Body);
return Expression.AndAlso(firstEqual, secondEqual);
})
.ToList();
while (predicates.Count > 1)
{
predicates = PairWise(predicates).Select(p => Expression.OrElse(p.Item1, p.Item2)).ToList();
}
var body = predicates.Single();
var clause = Expression.Lambda<Func<TQuery, bool>>(body, key1Selector.Parameters[0]);
return queryable.Where(clause);
}
class ReplaceParameterVisitor : ExpressionVisitor
{
private ParameterExpression _oldParameter;
private ParameterExpression _newParameter;
public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (ReferenceEquals(node, _oldParameter))
return _newParameter;
return base.VisitParameter(node);
}
}
/// <summary>
/// Break a list of items tuples of pairs.
/// </summary>
private static IEnumerable<(T, T)> PairWise<T>(this IEnumerable<T> source)
{
var sourceEnumerator = source.GetEnumerator();
while (sourceEnumerator.MoveNext())
{
var a = sourceEnumerator.Current;
sourceEnumerator.MoveNext();
var b = sourceEnumerator.Current;
yield return (a, b);
}
}
private static TKey[] Bucketize<TKey>(IEnumerable<TKey> values)
{
var distinctValueList = values.Distinct().ToList();
// Calculate bucket size as 1,2,4,8,16,32,64,...
var bucket = 1;
while (distinctValueList.Count > bucket)
{
bucket *= 2;
}
// Fill all slots.
var lastValue = distinctValueList.Last();
for (var index = distinctValueList.Count; index < bucket; index++)
{
distinctValueList.Add(lastValue);
}
var distinctValues = distinctValueList.ToArray();
return distinctValues;
}
In the absence of a general solution, I think there are two things to consider:
Avoid multi-column primary keys (will make unit testing easier too).
But if you have to, chances are that one of them will reduce the
query result size to O(n) where n is the size of the ideal query
result. From here, its Solution 5 from Gerd Arnold above.
For example, the problem leading me to this question was querying order lines, where the key is order id + order line number + order type, and the source had the order type being implicit. That is, the order type was a constant, order ID would reduce the query set to order lines of relevant orders, and there would usually be 5 or less of these per order.
To rephrase: If you have a composite key, changes are that one of them have very few duplicates. Apply Solution 5 from above with that.
I tried this solution and it worked with me and the output query was perfect without any parameters
using LinqKit; // nuget
var customField_Ids = customFields?.Select(t => new CustomFieldKey { Id = t.Id, TicketId = t.TicketId }).ToList();
var uniqueIds1 = customField_Ids.Select(cf => cf.Id).Distinct().ToList();
var uniqueIds2 = customField_Ids.Select(cf => cf.TicketId).Distinct().ToList();
var predicate = PredicateBuilder.New<CustomFieldKey>(false); //LinqKit
var lambdas = new List<Expression<Func<CustomFieldKey, bool>>>();
foreach (var cfKey in customField_Ids)
{
var id = uniqueIds1.Where(uid => uid == cfKey.Id).Take(1).ToList();
var ticketId = uniqueIds2.Where(uid => uid == cfKey.TicketId).Take(1).ToList();
lambdas.Add(t => id.Contains(t.Id) && ticketId.Contains(t.TicketId));
}
predicate = AggregateExtensions.AggregateBalanced(lambdas.ToArray(), (expr1, expr2) =>
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<CustomFieldKey, bool>>
(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
});
var modifiedCustomField_Ids = repository.GetTable<CustomFieldLocal>()
.Select(cf => new CustomFieldKey() { Id = cf.Id, TicketId = cf.TicketId }).Where(predicate).ToArray();
I ended up writing a helper for this problem that relies on System.Linq.Dynamic.Core;
Its a lot of code and don't have time to refactor at the moment but input / suggestions appreciated.
public static IQueryable<TEntity> WhereIsOneOf<TEntity, TSource>(this IQueryable<TEntity> dbSet,
IEnumerable<TSource> source,
Expression<Func<TEntity, TSource,bool>> predicate) where TEntity : class
{
var (where, pDict) = GetEntityPredicate(predicate, source);
return dbSet.Where(where, pDict);
(string WhereStr, IDictionary<string, object> paramDict) GetEntityPredicate(Expression<Func<TEntity, TSource, bool>> func, IEnumerable<TSource> source)
{
var firstP = func.Parameters[0];
var binaryExpressions = RecurseBinaryExpressions((BinaryExpression)func.Body);
var i = 0;
var paramDict = new Dictionary<string, object>();
var res = new List<string>();
foreach (var sourceItem in source)
{
var innerRes = new List<string>();
foreach (var bExp in binaryExpressions)
{
var emp = ToEMemberPredicate(firstP, bExp);
var val = emp.GetKeyValue(sourceItem);
var pName = $"#{i++}";
paramDict.Add(pName, val);
var str = $"{emp.EntityMemberName} {emp.SQLOperator} {pName}";
innerRes.Add(str);
}
res.Add( "(" + string.Join(" and ", innerRes) + ")");
}
var sRes = string.Join(" || ", res);
return (sRes, paramDict);
}
EMemberPredicate ToEMemberPredicate(ParameterExpression firstP, BinaryExpression bExp)
{
var lMember = (MemberExpression)bExp.Left;
var rMember = (MemberExpression)bExp.Right;
var entityMember = lMember.Expression == firstP ? lMember : rMember;
var keyMember = entityMember == lMember ? rMember : lMember;
return new EMemberPredicate(entityMember, keyMember, bExp.NodeType);
}
List<BinaryExpression> RecurseBinaryExpressions(BinaryExpression e, List<BinaryExpression> runningList = null)
{
if (runningList == null) runningList = new List<BinaryExpression>();
if (e.Left is BinaryExpression lbe)
{
var additions = RecurseBinaryExpressions(lbe);
runningList.AddRange(additions);
}
if (e.Right is BinaryExpression rbe)
{
var additions = RecurseBinaryExpressions(rbe);
runningList.AddRange(additions);
}
if (e.Left is MemberExpression && e.Right is MemberExpression)
{
runningList.Add(e);
}
return runningList;
}
}
Helper class:
public class EMemberPredicate
{
public readonly MemberExpression EntityMember;
public readonly MemberExpression KeyMember;
public readonly PropertyInfo KeyMemberPropInfo;
public readonly string EntityMemberName;
public readonly string SQLOperator;
public EMemberPredicate(MemberExpression entityMember, MemberExpression keyMember, ExpressionType eType)
{
EntityMember = entityMember;
KeyMember = keyMember;
KeyMemberPropInfo = (PropertyInfo)keyMember.Member;
EntityMemberName = entityMember.Member.Name;
SQLOperator = BinaryExpressionToMSSQLOperator(eType);
}
public object GetKeyValue(object o)
{
return KeyMemberPropInfo.GetValue(o, null);
}
private string BinaryExpressionToMSSQLOperator(ExpressionType eType)
{
switch (eType)
{
case ExpressionType.Equal:
return "==";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case ExpressionType.NotEqual:
return "<>";
default:
throw new ArgumentException($"{eType} is not a handled Expression Type.");
}
}
}
Use Like so:
// This can be a Tuple or whatever.. If Tuple, then y below would be .Item1, etc.
// This data structure is up to you but is what I use.
[FromBody] List<CustomerAddressPk> cKeys
var res = await dbCtx.CustomerAddress
.WhereIsOneOf(cKeys, (x, y) => y.CustomerId == x.CustomerId
&& x.AddressId == y.AddressId)
.ToListAsync();
Hope this helps others.
in Case of composite key you can use another idlist and add a condition for that in your code
context.Table.Where(q => listOfIds.Contains(q.Id) && listOfIds2.Contains(q.Id2));
or you can use one another trick create a list of your keys by adding them
listofid.add(id+id1+......)
context.Table.Where(q => listOfIds.Contains(q.Id+q.id1+.......));
I tried this on EF Core 5.0.3 with the Postgres provider.
context.Table
.Select(entity => new
{
Entity = entity,
CompositeKey = entity.Id1 + entity.Id2,
})
.Where(x => compositeKeys.Contains(x.CompositeKey))
.Select(x => x.Entity);
This produced SQL like:
SELECT *
FROM table AS t
WHERE t.Id1 + t.Id2 IN (#__compositeKeys_0)),
Caveats
this should only be used where the combination of Id1 and Id2 will always produce a unique result (e.g., they're both UUIDs)
this cannot use indexes, though you could save the composite key to the db with an index

Entity Query in Query, is it possible

I'm not really sure how to ask this question. I need to create an object, I believe it is called a projection, that has the result of one query, plus from that need to query another table and get that object into the projection.
This is a C# WCF Service for a Website we are building with HTML5, JS, and PhoneGap.
EDIT: Getting an error on the ToList (see code) - "The method or operation is not implemented."
EDIT3: changed the Entity Object company_deployed_files to IQueryable AND removed the FirstOrDefault caused a new/different exception Message = "The 'Distinct' operation cannot be applied to the collection ResultType of the specified argument.\r\nParameter name: argument"
Background: This is a kind of messed up Entity Model as it was developed for Postgresql, and I don't have access to any tools to update the model except by hand. Plus some design issues with the database does not allow for great model even if we did. In other words my two tables don't have key constrains(in the entity model) to perform a join in the entity model - unless someone shows me how - that honestly might be the best solution - but would need some help with that.
But getting the below code to work would be a great solution.
public List<FileIDResult> GetAllFileIDFromDeviceAndGroup ( int deviceID, int groupID)
{
List<FileIDResult> returnList = null;
using (var db = new PgContext())
{
IQueryable<FileIDResult> query = null;
if (deviceID > 0)
{
var queryForID =
from b in db.device_files
where b.device_id == deviceID
select new FileIDResult
{
file_id = b.file_id,
file_description = b.file_description,
company_deployed_files = (from o in db.company_deployed_files
where o.file_id == b.file_id
select o).FirstOrDefault(),
IsDeviceFile = true
};
if (query == null)
{
query = queryForID;
}
else
{
// query should always be null here
}
}
if (groupID > 0)
{
var queryForfileID =
from b in db.group_files
where b.group_id == groupID
select new FileIDResult
{
file_id = b.file_id,
file_description = b.file_description,
company_deployed_files = (from o in db.company_deployed_files
where o.file_id == b.file_id
select o).FirstOrDefault(),
IsDeviceFile = false
};
if (query != null)
{
// query may or may not be null here
query = query.Union(queryForfileID);
}
else
{
// query may or may not be null here
query = queryForfileID;
}
}
//This query.ToList(); is failing - "The method or operation is not implemented."
returnList = query.ToList ();
}
return returnList;
}
Edit 2
The ToList is throwing an exception.
I'm 98% sure it is the lines: company_deployed_files = (from o in db.company_deployed_files where o.file_id == b.file_id select o).FirstOrDefault()
End Edit 2

LINQ To SQL Weird Join Issue

I have a simple database with two tables. Users and Configurations. A user has a foreign key to link it to a particular configuration.
I am having a strange problem where the following query always causes an inner join to the Configuration table regardless of the second parameter value. As far as I can tell, even though the "UserConfiguration =" part of the object initialisation is conditional, LINQ doesn't see that and determines that a relationship is followed in any case.
If I actually remove that last initialisation, the whole thing works as expected. It doesn't inner join when loadConfiguration == false and it does join when loadConfiguration == true.
Anyone got any ideas about this? Is this syntax just not going to work? The only thought I have now is to wrap the return in a basic if statement - I just wanted to avoid the duplicated lines.
public UserAccount GetByUsername(string username, bool loadConfiguration)
{
using (Database database = new Database())
{
if (loadConfiguration)
{
DataLoadOptions loadOptions = new DataLoadOptions();
loadOptions.LoadWith<User>(c => c.Configuration);
database.LoadOptions = loadOptions;
}
return (from c in database.Users
where c.Username == username
select new UserAccount
{
ID = c.ID,
ConfigurationID = c.ConfigurationID,
Username = c.Username,
Password = c.Password.ToArray(),
HashSalt = c.HashSalt,
FirstName = c.FirstName,
LastName = c.LastName,
EmailAddress = c.EmailAddress,
UserConfiguration = (loadConfiguration) ? new ApplicationConfiguration
{
ID = c.Configuration.ID,
MonthlyAccountPrice = c.Configuration.MonthlyAccountPrice,
TrialAccountDays = c.Configuration.TrialAccountDays,
VAT = c.Configuration.VAT,
DateCreated = c.Configuration.DateCreated
} : null
}).Single();
}
}
Thanks in advance,
Martin.
I dont think it will work like that.
I suggest splitting it into 2 distinct queries.
There are probably better ways, but it would require more 'plumbling'.
Nope, this will not work. I've run into similar issues many times. The reason for that has to do with expressions at compile time vs. conditions at run-time.
You could do 2 queries or if you don't mind joining to configuration regardless of loadConfiguration parameter, you could use:
var q = (from c in database.Users
where c.Username == username
select c).Single();
and then use Linq-to-Objects on the result.
Replace .Single() with SingleOrDefault() and Linq will switch to a left outer join. I don't know if in your case it will do it, but in some cases it does.
Edit dint's see the Single was for the entire query and not for the configuration part:
try this:
UserConfiguration = (loadConfiguration && c.Configuration != null) ? new ApplicationConfiguration
{
ID = c.Configuration.ID,
MonthlyAccountPrice = c.Configuration.MonthlyAccountPrice,
TrialAccountDays = c.Configuration.TrialAccountDays,
VAT = c.Configuration.VAT,
DateCreated = c.Configuration.DateCreated
} : null

Categories