Reuse select expressions in LINQ to Entities - c#

I have some complex business definitions that I'd like to define once and reuse them in Linq to Entities and also use them in other expressions that build upon it.
My attempt is below, and this worked when I was originally passing the ConvertOrderDetailsToViewModel a List, but I want to perform this on an IQueryable, which results in the error:
LINQ to Entities does not recognize the method 'System.Decimal Invoke(OrderDetail)' method, and this method cannot be translated into a store expression.
Is there a way to achieve this natively (without 3rd party libraries)?
Looks like from this answer, you can define these functions in the database itself, and then call them from C#, but again, looking to do this solely in C# code if possible.
Also came across this answer which looks like it works for a Where expression, but when I try to implement it for my select expression, I get the this error, which I get, because I am trying to assign the expression to a decimal.
Cannot implicitly convert type Expression<Func<OrderDetail, decimal>> to 'decimal'
Here's the expression function:
public static Expression<Func<OrderDetail, decimal>> calcQtyNeedOD = (od) => (od.OrderedQuantity - od.PickedQuantity);
View model creation method that calls this function:
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
var qryAllocNeedVMTEST =
from od in qryShipOrderDtls
select new AllocationNeedViewModel()
{
WorkReleaseHeaderId = od.OrderHeader.WorkReleaseHeaderId.Value,
OrderHeaderId = od.OrderHeaderId,
OrderDetailId = od.Id,
ItemId = od.ItemId.Value,
ItemDescription = od.Item.Description,
IsInventoryTracked = od.Item.TrackInventory,
QtyNeed = calcQtyNeedOD(od),
QtyRemain = 0,
QtyAllocated = 0,
IsAllocated = false
}
;
return qryAllocNeedVMTEST.ToList();
}
To Make this more complex, there are other properties that I also want to have a reusable expression for, which would also use this first expression...i.e.
public static readonly Expression<Func<OrderDetail, decimal>> calcQtyRemainOD =
(od) => calcQtyNeedOD.Compile().Invoke(od) - calcQtyAllocatedOD.Compile().Invoke(od);
UPDATE #1
SEE UPDATE #2...this solution does NOT work!
Though so far no one has been able to provide a native way to reuse select expressions across queries, I did find a partial solution that works in being able to reuse them within the same query. Once you project/assign an expression to an entity's property, you can (unlike in T-SQL) then specify that property as part of another subsequent property expression.
Example - this shows the full expressions for each property projection. In this example, QtyRemain is essentially just QtyNeed - QtyAllocated. I was re-specifying those again in the QtyRemain assignment:
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
var qryAllocNeedVM = qryShipOrderDtls
.Select(od => new AllocationNeedViewModel() //Get all Work Order Detail Needs for Work Release
{
QtyNeed = (od.OrderedQuantity - od.PickedQuantity),
QtyAllocated = (od.AllocatedInventories.Count == 0 ? 0 : od.AllocatedInventories.Where(ai => ai.StatusId < _AllocatedInventoryProcessedStatus).Sum(ai => ai.AllocatedQty)),
QtyRemain = (od.OrderedQuantity - od.PickedQuantity) - (od.AllocatedInventories.Count == 0 ? 0 : od.AllocatedInventories.Where(ai => ai.StatusId < _AllocatedInventoryProcessedStatus).Sum(ai => ai.AllocatedQty))
}
);
return qryAllocNeedVM.ToList();
}
Instead, you can simply use the already defined properties in that QtyRemain property assignment, like so:
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
var qryAllocNeedVM = qryShipOrderDtls
.Select(od => new AllocationNeedViewModel() //Get all Work Order Detail Needs for Work Release
{
QtyNeed = (od.OrderedQuantity - od.PickedQuantity),
QtyAllocated = (od.AllocatedInventories.Count == 0 ? 0 : od.AllocatedInventories.Where(ai => ai.StatusId < _AllocatedInventoryProcessedStatus).Sum(ai => ai.AllocatedQty)),
QtyRemain = this.QtyNeed - this.QtyAllocated
}
);
return qryAllocNeedVM.ToList();
}
Though this is not a full solution to my original question, it is a partial solution that gets you some of the benefits desired.
UPDATE #2
I was wrong on UPDATE #1. Though this works and compiles and seems to generate SQL, it is not correct. The returned value of this.QtyNeed when being used in subsequent expressions always results to 0. :(

Have you considered the following?:
public static Func<OrderDetail, decimal> calcQtyNeedOD = (od) => (od.OrderedQuantity - od.PickedQuantity);
public List<AllocationNeedViewModel> ConvertOrderDetailsToViewModel(IQueryable<OrderDetail> qryShipOrderDtls, List<int> itemIdsToExclude, bool boolGroupViewModelByItemAndRemnant)
{
return qryShipOrderDtls
.ToArray()
.Select(new AllocationNeedViewModel
{
WorkReleaseHeaderId = od.OrderHeader.WorkReleaseHeaderId.Value,
OrderHeaderId = od.OrderHeaderId,
OrderDetailId = od.Id,
ItemId = od.ItemId.Value,
ItemDescription = od.Item.Description,
IsInventoryTracked = od.Item.TrackInventory,
QtyNeed = calcQtyNeedOD(od),
QtyRemain = 0,
QtyAllocated = 0,
IsAllocated = false
}).ToList();
}
The original query was trying to execute the Func against the actual database (which won't understand your expression). Since you are not filtering your query (no where clause), return the entire set so that you can project using your local Func.

Related

Assigning object properties based on a ternary conditional inside a linq query

in the code below I'm assigning data retrieved from an API to a variable called groupedGamesListing.
With that variable, I'm selecting the data and creating a new Object called GroupedGames.
I had it working, but now I'm trying to introduce a new property called GameControllerId.
I need GameControllerId to be one of two numbers, depending on a year.
I started to write the code, but I can't figure out how to represent an if/then or Ternary Operator inside the lambda expression.
Here is what I'm working with.
var groupedGamesListing = apiResults.Select(games => new GroupedGames
{
// ...
GameId = games.Select(g => g.CurrentGameId).FirstOrDefault().ToString(),
GameControllerId = games.Select(g => { (g.GameYear == 2018) ? 34729 : 99483 }),
// ...
}).ToList();
So if the g.GameYear is 2018, then I need the GameControllerId to be 34729. If not, then 99483.
Is this possible inside the expression above?
Thanks!
I'm not sure about your exact use case, but you could consider
var groupedGamesListing = apiResults.SelectMany(g => new GroupedGames
{
// ...
GameId = g.CurrentGameId.ToString(),
GameControllerId = (g.GameYear == 2018) ? 34729 : 99483,
// ...
}).ToList();
It looks like you might be running into issues from apiResults being a list of lists, so this would flatten the original object.

Entity framework condidional query building

I have problem with creating complex query with entity framework. I would like to fetch additional data into my linq entity based on parameters given during construction of such query. Here is example with where:
if (featureEnabled)
{
query = query.Where(n => *condition*);
}
I have complex object created like that:
n => new Entity{
Property = n.Something
\* ... *\
PropertyN = n.SomethingN,
}
and I want to load additional data into entity if feature is enabled (just like in where example):
public DoSomething(bool featureEnabled, feature2Enabled, etc.)
{
return n => new Entity{
Property = n.Something,
\* ... *\
PropertyN = n.SomethingN,
Feature = (featureEnabled) ? *fetch some data from navigation property* : 0,
Feature2 = (feature2Enabled) etc.
}
}
In above example parameters (featureNEnabled) will be translated into sql parameters. How to perform such operation at query construction time?
did you mean inside the initializer you want use the if condition?
if its so i will have its not possible. To use the if condition you have to put it outside the initializer
var a = new MyClass{
prop1 = n.prop1,
prop2 = n.prop2,
prop3 = n.prop3,
};
a.propN = boolCondition ? n.PropN : 0;
I finally found answer to my question on this blog
With this code you may call expression.Merge(expression2) and initialization lists of two objects will be merged into one query.

Entity Framework workaround for query based on combination of primitives in memory

I have a class T:
class T {
int Prop1Id;
int Prop2Id;
}
At runtime, in memory, I have:
var L1 = new List<T>();
And L1 contains the following T objects:
Prop1 Prop2
12 5
6 7
8 9
10 12
I cannot do the following:
attributes.Where(x=>L1.Any(y=>y.Prop1ID == x.Prop1ID && y.Prop2ID == x.Prop2ID))
attributes is an EF type (i.e: in the DB)
This does not work because it cannot create a constant value type of T - it expects only primitives and enumerables.
Has anyone figured out a workaround other than bring back all of attributes so they are in memory as IEnumerable instead of IQueryable? For me, that's not really an option :-(
Suppose with what you provided, the 2 properties are of integer type. So you can try this work-around. We need to convert your list into another list of string keys like this:
var stringKeys = L1.Select(e => e.Prop1 + "_" + e.Prop2);
//then use stringKeys normally
attributes.Where(x=> stringKeys.Contains(x.Prop1ID + "_" + x.Prop2ID));
If it's not the case (not limited to integer properties), we may have to do it in a more complicated way by building the Where conditions based on the list L1:
var result = attributes;
foreach(var e in L1){
var k1 = e.Prop1;
var k2 = e.Prop2;
var sub = attributes.Where(x => k1 == x.Prop1ID && k2 == x.Prop2ID);
result = result.Concat(sub);
}
//then access result instead of attributes
The above approach may be a bit less efficient. It will execute L1.Counttimes of WHERE against the database table before concatenating (UNION ALL) all results. However I believe if it's performed on key columns, the performance is still good.
The last solution is try using Expression tree (representing the predicate passed in Where):
//suppose your attributes is a set of Attribute
var p = Expression.Parameter(typeof(Attribute));
var p1 = Expression.Property(p, "Prop1ID");
var p2 = Expression.Property(p, "Prop2ID");
var initBool = Expression.Constant(false) as Expression;
//build the BodyExpression for the predicate
var body = L1.Aggregate(initBool, (c,e) => {
var ep1 = Expression.Constant(e.Prop1);
var ep2 = Expression.Constant(e.Prop2);
var and = Expression.And(Expression.Equal(ep1, p1), Expression.Equal(ep2,p2));
return Expression.Or(c, and);
});
attributes.Where(Expression.Lambda<Func<Attribute, bool>>(body, p));
Note that the type of x.Prop1ID should exactly match the type of each element of L1.Prop1 (such as should be both int or float, ...). The same condition applies to all corresponding properties. Otherwise you need to modify the code to build the Expression tree (the reason here is Expression.Equal requires the 2 operand expressions to have the same data type). I've not tested the last code using Expression tree but it's very clear on the idea.
The last option involving Expression (internally) is try using LinqKit. I've not even used this library once but it can help you be free from building Expression tree in many cases. So give it a try if it can help you run your original query without getting the exception you mentioned.

IEnumerable Select statement with ternary operator

I have an odd behavior using an IEnumerable<string> with a ternary operator and a Select statement.
I have two lists with different objects. One list contains Enums the other list contains objects. Those objects do have a String property.
If one list is null or empty I want to get the values of the other list.
Here is some code:
public class ExportItem
{
public string Type;
...
}
public enum ExportType
{
ExportType1,
ExportType2,
...
}
The List<ExportItem> is always filled by a config file. The List<ExportType> is filled if command line arguments are provided. So if List<ExportType> is filled I want to use them, otherwise I want to use those from the config file.
So my code ist like this:
IEnumerable<string> exportTypes = MyListOfExportTypes != null &&
MyListOfExportTypes.Any() ? MyListOfExportTypes.Select(x => x.ToString()) :
MyListOfExportItems.Select(x => x.Type);
The thing is that exportTypes is null but I don't get it...
When I do this with if-else everything works as expected. Also if exportTypes is of type List<string> and I call ToList() after the Select statement everything works fine.
Using var a = MyListOfExportTypes.Select(x => x.ToString()); and var b = MyListOfExportItems.Select(x => x.Type); does work as expected.
Must be something with the ternary operator and/or IEnumerable. But what?
Or what do I miss? Any suggestions?
EDIT:
I now have a screenshot...
Note that the code above foreach works nevertheless...
Not sure if this was answered,
But I think that this is related to the fact that you are using LINQ deferred execution.
When writing LINQ queries,
there is a difference between creating the query and executing it.
Writing the select statement, is creating the query, adding ToList() executes it.
Think of it like writing SQL query in SQL server console (that's the writing stage),
and once you hit F5 (Or the play button) you execute it.
I hope this little code sample will help to clarify it.
public class SomeClass
{
public int X { get; set; }
public int Y { get; set; }
public void Test()
{
//Here I'm creating a List of Some class
var someClassItems = new List<SomeClass> {
new SomeClass { X = 1, Y = 1 },
new SomeClass { X = 2, Y = 2 }
};
//Here I'm creating a Query
//BUT, I'm not executing it, so the query variable, is represented by the IEnumerable object
//and refers to an in memory query
var query = someClassItems.
Select(o => o.X);
//Only once the below code is reached, the query is executed.
//Put a breakpoint in the query, click the mouse cursor inside the select parenthesis and hit F9
//You'll see the breakpoint is hit after this line.
var result = query.
ToList();
}
}

Validating ASP.NET MVC 3 controller's action params when using Dynamic Expressions API

I have a standard ASP.NET MVC 3 Controller with an action that has following signature:
public ActionResult Index(int? page, string sort, string sortDir)
My view is using WebGrid so parameters are produced by it automatically.
Next I use Dynamic Expressions API (aka Dynamic LINQ) to convert parameters into query. Example:
var customerSummary = CustomerManager.CustomerRepository.GetQuery()
.OrderBy(sort + " " + sortDir)
.Select(c => new CustomerSummaryViewModel()
{
Id = c.Id,
Name = c.Name,
IsActive = c.IsActive,
OrderCount = c.Orders.Count
})
.Skip(page.Value - 1 * 10) //10 is page size
.Take(10)
.ToList();
The Goal
What I would like to do is to use Dynamic Expressions API itself to validate parameters for sorting (and perhaps create a valid lambda). For example, I'd like to use DynamicExpression.Parse() or DynamicExpression.ParseLambda() methods to see if they produce ParseException, in which case I can replace erroneous params with default (e.g. sort by name ascending "Name ASC")...
The Problem
The problem is that IQueryable extensions take only a string
If I wanted to use ParseLambda and then feed it to .OrderBy I cannot use direction (it takes in only property name). For example, I can do this:
var se = DynamicExpression.ParseLambda<Customer, string>("Name"); // now I can use .OrderBy(se) which is same as .OrderBy(c=>c.Name)
but not this
var se = DynamicExpression.ParseLambda<Customer, string>("Name DESC");
Recap
I would like to use Dynamic LINQ to 1) validate and 2) build predicates (for sorting) based on action parameters
I'm not very familiar with Dymaic LINQ but you can do the following:
var customerSummary = CustomerManager.CustomerRepository.GetQuery();
if ("desc".Equals(sortDir, StringComparison.CurrentCultureIgnoreCase))
customerSummary = customerSummary.OrderByDescending(sort);
else
customerSummary = customerSummary.OrderBy(sort);
var pageNumber = page.GetValueOrDefault();
if (pageNumber < 1)
pageNumber = 1;
customerSummary = customerSummary
.Select(c => new CustomerSummaryViewModel()
{
Id = c.Id,
Name = c.Name,
IsActive = c.IsActive,
OrderCount = c.Orders.Count
})
.Skip((pageNumber - 1) * 10)
.Take(10)
.ToList();
I done something similar (except i used raw Expressions) in my project but gone even further.
I've created a base ViewModel like this:
class TableViewModel
{
public string SortColumn { get; set; }
public bool IsAsc { get; set; }
public int? PageNumber { get; set; }
}
And created a helper method that do all the paging/sorting work. Signature is like this:
public static IQueryable<T> TableHelper(this IQueryable<T> source, TableViewModel model) { ... }
And when i need to receive data from my table control and return a requested piece of data, controller action looks like this:
public ActionResult Index(TableViewModel model)
{
var data = _productRepository.AsQueryable().TableHelper(model);
... //Operation on data
}
Common thing that before and after call of the helper you are free to apply any filtering or smth.
It's very convinient.
When i need to extend my ViewModel, i inherit it and add new members to child model.
UPD: If you decide to stay DLINQ, try the following signature - OrderBy("Name", "ascending");
UPD2: If you want to validate your sort parameter, i think, reflection is the only choise. Something like this:
bool doSort = typeof(Product).GetProperty(sort, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy /*Or whatever flags you need*/) != null
Going this way, you should apply a OrderBy/OrderByDescending logic only if doSort is true. Otherwise, just skip sorting or apply any default logic.
In my oppinion, the more functionality you want from your code, the less DLINQ seems to be suitable for it. Once PropertyInfo is obtained the next reasonable step could be to use it in expression. Once we do it, where would be a place for DLINQ? :)
I agree that the expression and reflection code is very ugly in actions, but moving it outside, for example, to ExtensionMethod, as in my case, or in NonAction mehod of a controller base class, saves your eyes from seeing it:)

Categories