C# linq database optimization - c#

I have the following C# code:
MyObject myObj= dbCtx.MyObjects.FirstOrDefault(
p => p.Src.AccNum == srcAccNum && p.Dest.AccNum == destAccNum);
It executes fine. However, I would prefer to only query the database if p.SrcId < p.DestId
Is this possible and how would I code it?

Just add it to the predicate.
Querying the DbSet (or an IQueryable) will filter the data directly in database. That is, the predicate will be translated to SQL and just the data which match the predicate will be will be loaded in memory.
MyObject myObj = dbCtx.MyObjects.FirstOrDefault(
p => p.SrcId < p.DestId && p.Src.AccNum == srcAccNum && p.Dest.AccNum == destAccNum);

Related

Use Expression<Func<MyEntity, bool>> in Linq on MyEntity.<OtherEntity>.<MyProperty>

I am trying to simplify a method that returns an IQueryable
A, B and C extend BaseEntity containing a enum that I want to compare.
context is a entity framework dbcontext.
Here is a stripped down version of the method:
return context.MyEntities.Include("A").Include("B").Include("C")
.Where(x => x.A.MyEnum == MyEnum.<value> && x.B.MyEnum == MyEnum.<value> && x.C.MyEnum == MyEnum.<value>);
I tried to do this:
Func<BaseEntity, bool> equals = x => x.MyEnum == MyEnum.<value>;
return context.MyEntities.Include("A").Include("B").Include("C")
.Where(x => equals(x.A) && equals(x.B) && equals(x.C));
It compiles but gives a runtime error. For what I understand is that Linq cannot translate the func<> to SQL?
So i searched and I found that you need to wrap the func<> in an expression<>, so Linq can compile it and translate it to SQL.
Now I have this:
Expression<Func<BaseEntity, bool>> equals = x => x.MyEnum == MyEnum.<value>;
return context.MyEntities.Include("A").Include("B").Include("C")
.Where(x => equals(x.A) && equals(x.B) && equals(x.C));
But this doesn't compile: 'Method name expected'.
Is there a way to accomplish what i'm trying to do?
The compile error is because you have to first compile the expression before being able to invoke it.
equals.Compile()(x.A)
But this defeats the purpose of using the expression to begin with.
There is nothing to be simplified in the provided code other than moving the repeated value call into a variable.
var value = MyEnum.<value>;
return context.MyEntities.Include("A").Include("B").Include("C")
.Where(x => x.A.MyEnum == value && x.B.MyEnum == value && x.C.MyEnum == value);
The attempt to simplify what was shown is not really needed.

Building efficient left anti-semi joins in EF6

I've been converting a long-standing, gnarly sproc into EF to get it into a more testable and maintainable position. Most of it has gone fairly well, except for this part here:
select p.idProduct from products p
where
// {some random filtering bringing us down to a manageable number of rows}
AND p.idProduct NOT IN (SELECT idProduct from productsShipped)
which I have converted into this:
var results = dbc.products.Where(p =>
p.warehouse == warehouse
&& p.BarConversion.Bar.BarDate > minDate
&& !dbc.productsShipped.Any(ps => ps.idInventory == p.idInventory)
//&& p.productsShipped == null
&& p.OPR.Order.Payment != null
&& !(p.OPR.Order.PaymentType == 5 &&
(p.OPR.Order.Payment.paymentStatus == null ||
p.OPR.Order.Payment.paymentStatus != "accepted"))
&& p.OPR.Order.OrderSla.expectedShipDate <= dueDateCutoff);
The issue I'm having is that the productsShipped table is absolutely enormous. In raw SQL, the where clause must understand that it does not need to pull the entirety of the productsShipped table and instead only fetches products which relate to the previous query. The EF equivalent breaks it down into a subquery and asks for every entry in the productsShipped table, causing the query to take more than five minutes, as opposed to the couple of seconds it takes to run without this filter. I've tried adding a relationship between the two entities with similar results.
Is there a way I can force Entity to make a proper left outer exclusive join rather than a subquery or similarly improve performance, or am I forced to either take this performance hit or push part of my logic into a difficult-to-test sproc?
Here is an extension method (from MSDN) for a left anti-semijoin:
public static IQueryable<TLeft> LeftAntiSemiJoin<TLeft, TRight>(this IQueryable<TLeft> left, IQueryable<TRight> right, Expression<Func<TLeft, TRight, bool>> predicate) {
var leftPrm = predicate.Parameters[0];
var rightPrm = predicate.Parameters[1];
// retrieve methods
var anyMethod = ((Func<IQueryable<TRight>, bool>)Queryable.Any).Method;
var whereMethod = ((Func<IQueryable<TRight>, Expression<Func<TRight, bool>>, IQueryable<TRight>>)Queryable.Where).Method;
// l => !right.Where(r => predicate(l, r)).Any()
var leftPredicate = Expression.Lambda<Func<TLeft, bool>>(
Expression.Not(
Expression.Call(anyMethod,
Expression.Call(whereMethod,
Expression.Constant(right),
Expression.Lambda<Func<TRight, bool>>(predicate.Body, rightPrm)))),
leftPrm);
return left.Where(leftPredicate);
}
Which you can use like this:
var results2 = dbc.products.LeftAntiSemiJoin(dbc.productsShipped, (p, ps) => p.idInventory == ps.idInventory)
.Where(p =>
p.warehouse == warehouse &&
p.BarConversion.Bar.BarDate > minDate &&
p.OPR.Order.Payment != null &&
!(p.OPR.Order.PaymentType == 5 &&
(p.OPR.Order.Payment.paymentStatus == null ||
p.OPR.Order.Payment.paymentStatus != "accepted")) &&
p.OPR.Order.OrderSla.expectedShipDate <= dueDateCutoff);
Perhaps it will be faster?
While I was ultimately unable to get Entity to generate the SQL the way I wanted, I found I was able to run a separate query to get the necessary data from productsShipped, drop that into a dictionary, and do the lookup from there.

ASP.NET Entity Framework .Where() with Lesser than

How can I make this lesser than or equal work in my .Where() clause? I am getting an error.
var filteredProducts = Products.Where(p => p.State.Contains("Bruikbaar"))
.Where(p => p.Privilege <= ui.GetPrivilegeNumber())
.ToList();
Error:
LINQ to Entities does not recognize the method 'Int32 GetPrivilegeNumber()' method, and this method cannot be translated into a store expression.
I hope this question is never asked before. Googled couldn't find it either or I am using the wrong words to express my problem.
ui.GetPrivilegeNumber() is not a recognized method.
Use this:
var uiPrivilege = ui.GetPrivilegeNumber();
var filteredProducts = Products.Where(p => p.State.Contains("Bruikbaar"))
.Where(p => p.Privilege <= uiPrivilege)
.ToList();
And as other users mentionted, you can optimize your Where.
EF does not execute method calls which you use in predicates. It stores them as expression (i.e. syntax tree) and then analyzes this tree to build SQL query by translating C# code to SQL code. It cannot translate GetPrivilegeNumber() method call into SQL, because there is no appropriate SQL code for that. So all you need is move this method call out of expression and pass only result of method call instead:
var privilegeNumber = ui.GetPrivilegeNumber();
var filteredProducts = Products.Where(p => p.State.Contains("Bruikbaar"))
.Where(p => p.Privilege <= privilegeNumber)
.ToList();
Now privilegeNumber is just an integer variable which is translated into SQL parameter
SELECT * FROM Products p
WHERE p.State LIKE '%Bruikbaar%' AND p.Privilege <= #privilegeNumber
You need to move ui.GetPrivilegeNumber() outside of the query. You can also merge those Where queries into a single one:
var privilegeNumber = ui.GetPrivilegeNumber();
var filteredProducts = Products.Where(p =>
p.State.Contains("Bruikbaar")
&& p => p.Privilege <= privilegeNumber)
.ToList();
You can use other evaluation method inside LinQ. To simplified the code, you can use it in little old way of writing LinQ.
var uiPrivilege = ui.GetPrivilegeNumber();
var filteredProducts =(from p in Products
where p.State.Contains("Bruikbaar") && p.Privilege <= uiPrivilege
select p).ToList();
The above query generate same output but easy to understood.

Entity Framework - Linq to Entities - Optional filter

I am struggling to figure out how to get a LINQ statement to produce a specific WHERE clause in SQL in a single statement.
I am after it producing something like this:
SELECT ColA, ColB, ColC, ColN...
FROM Orders
WHERE Client = #ClientId
AND (#CompanyId IS NULL OR #CompanyId = CompanyId)
My (failing) LINQ statement looks like this:
var includeAllCompanies = company == null;
var data = context.Orders.Where(o => o.Client.Id == clientId
&& (includeAllCompanies
|| (c.Company != null && c.Company.Id == company.Id)).ToList();
However, it always throws an exception when the variable company is NULL (it works fine when it has been initialised). The exception being:
Non-static method requires a target.
My current fix is to split my LINQ statement into two. One using an Expression<Func<>> (to be transformed to a SQL statement with partial filtering). Then another that uses Func<> to perform the remaining filters on the returned list.
Expression<Func<>> to let SQL do some of the work (excluding nullable objects)
var data = context.Orders.Where(o => o.Client.Id == clientId).ToList();
Func<> to then filter out the nullable objects
data = data.Where(c => (territory == null
|| (c.Territory != null && c.Territory.Id == territory.Id))).ToList();
This works, however, I want SQL to be performing this query.
The problem is that, company is server-side variable. Regardles includeAllCompanies value, EF has to translate whole LINQ query to SQL - and in this case SQL doesn't know what is company.Id - so EF has to always get company.Id value in order to put into SQL query. Even if company is null (so that is why you get exception). I hope you see my point, if not - I'll try to give some sample.
In order get rid of exception you can do the following:
var companyId = company == null ? null : (int?)company.Id;
var data = context.Orders.Where(o => o.Client.Id == clientId
&& (companyId == null
|| (c.Company != null && c.Company.Id == companyId)).ToList();

How can I parse a value in linq to entity?

I want to do something like this:
var apps = from app in context.Apps
where (platform == AppPlatform.All ||
(app.Platform == sPlatform && new Version(app.PlatformVersion) <= version))&&
(availability == AppAvailability.All || app.Availability == sAvailability)
select app;
return apps.ToList();
The line new Version(app.PlatformVersion) <= version)) is causing an error: Only parameterless constructors and initializers are supported in LINQ to Entities.
Basically what I need is to make my entity model parse the app.PlatformVersion as a new Version() object, instead of string, but I apparently can't do this from within my linq-to-entity. Can I do this at the entity-model level? I have other fields (strings) that I'd like to parse into types as well (like parsing a string to an Enum). How would I accomplish this?
Because EF tries to translate the Linq query to SQL and there is no way to translate Version method. So you could just query with other conditions first and store it in memory, then query from the in-memory object using the complex Linq query.
Technically you could do this, (you can definitely make it better in terms of the performance)
var apps_temp = from app in context.Apps.All().ToList();
//this query will not be translated to SQL.
var apps = from app in apps_temp
where (platform == AppPlatform.All ||
(app.Platform == sPlatform && new Version(app.PlatformVersion) <= version))&&
(availability == AppAvailability.All || app.Availability == sAvailability)
select app;
return apps.ToList();

Categories