Joins of two table with Lambda expression - c#

I am new to Lambda expression. I want a result from combination of two tables with where clause in lambda Expression , query runs fine but how to get result in variable after processing query??
var Rental = db.AUCDATA_COUPONS.Join(db.AUCDATA_TENORS,
c => c.AUCDT_ID,
o => o.AUCDT_ID,
(c, o) => new { c, o })
.Where(x => x.o.PRODUCT_ID == "SUKUK" && x.o.ISSUE_DATE == Convert.ToDateTime("02-MAR-12") && x.o.TENOR_ID == "03Y"
&& x.c.AUCDT_ID == x.o.AUCDT_ID && x.c.COUPON_NXTDT == Convert.ToDateTime("21-NOV-15"))
.Select(x => x.c.RENTAL_RATE);

db.AUCDATA_COUPONS is an IQueryable<T> (where T is the type of the class representing the table). The extension methods you use (like Join, Where and Select) take this IQueryable<T> and return a new IQueryable<T>.
The last Select returns an IQueryable<int> (or double depending of the type of RENTAL_RATE). The actual query (the lambdas) are only executed when you iterate through that IQueryable. You can do that with foreach
foreach(var rentalRate in Rental)
Maybe a better way is to convert the result to a list or array. This way you would execute the query only once and not again and again with every foreach you execute:
var list = Rental.ToList(); // results in an List<int>
// or
var array = Rental.ToArray(); // results in an int[]
Note that you'll probably need to change your datetime comparisons to
x.o.ISSUE_DATE.Date == new DateTime(2012,3,2)
and
x.c.COUPON_NXTDT.Date == new DateTime(2015,11,21)
for the query to work correctly.

You already have the results in the variable. But, depending on what you want to do with them, you can add .ToArray() or .ToList() after .Select(...).

Related

how to compare values from a list in Linq to Entity (any / contains)

There is a list, policiesToDelete of entity class, MonitoringRelations. Out of this list I have selected two elements and construed a new list:
var policyKeysToDelete = policiesToDelete
.Select(r => new {r.PolicyId, r.GroupId})
.ToList();
Now, I have a query where I want to compare elements from policyKeysToDelete list.
var objectsToDelete = (from p in storageContext.MonitoringRelations
where policyKeysToDelete
.Any(x => x == new {p.PolicyId, p.GroupId})
select p)
.ToList();
The problem: the query above throws this exception:
NotSupportedException: Unable to create a constant value of type 'Anonymous type'. Only primitive types or enumeration types are supported in this context.
I have tried changing the anonymous list to a list<tuple<PolicyId, GroupId>> , but that also didn't help, throwing the almost same exception. I tried using Contains in place of Any but that also didn't help.
Any idea how can I solve this problem?
EF cannot translate a list of complex objects into the SQL query. What EF can do, is translate a list of simple values into SQL when you use it with the .Contains method.
So if you extract a list of PolicyId's and a list of GroupId's from the policyKeysToDelete, and use it to select as much as you can with EF, then you can do the full check in the resultset which is then in-memory, using Linq-to-objects.
Warning: you are extracting too much from the database, so depending on the amount of data, a different solution might be better.
var policyKeysToDelete = policiesToDelete
.Select(r => new {r.PolicyId, r.GroupId})
.ToList();
// List of values types, which can be translated to SQL
var policyIds = policyKeysToDelete.Select(x => x.PolicyId).ToList();
var groupIds = policyKeysToDelete.Select(x => x.GroupId).ToList();
var objectsToDelete = storageContext.MonitoringRelations
// Do the part that we can do in the database, which is select the records
// which have an corresponding PolicyId or GroupId
.Where(x => policyIds.Contains(x.PolicyId) || groupIds.Contains(x.GroupId))
// Use this method to indicate that whatever follows after should not be
// translated to SQL
.AsEnumerable()
// Do the full check in-memory
.Where(x => policyKeysToDelete
.Any(y => x.PolicyId == y.PolicyId && x.GroupId == y.GroupId)
)
.ToList();

How to set multiple values in a list using lambda expression?

How to set multiple values of a list object, i am doing following but fails.
objFreecusatomization
.AllCustomizationButtonList
.Where(p => p.CategoryID == btnObj.CategoryID && p.IsSelected == true && p.ID == btnObj.ID)
.ToList()
.ForEach(x => x.BtnColor = Color.Red.ToString(), );
In the end after comma i want to set another value.
What should be the expression, although i have only one record relevant.
Well personally I wouldn't write the code that way anyway - but you can just use a statement lambda:
A statement lambda resembles an expression lambda except that the statement(s) is enclosed in braces
The body of a statement lambda can consist of any number of statements; however, in practice there are typically no more than two or three.
So the ForEach call would look like this:
.ForEach(x => {
x.BtnColor = Color.Red.ToString();
x.OtherColor = Color.Blue.ToString();
});
I would write a foreach loop instead though:
var itemsToChange = objFreecusatomization.AllCustomizationButtonList
.Where(p => p.CategoryID == btnObj.CategoryID
&& p.IsSelected
&& p.ID == btnObj.ID);
foreach (var item in itemsToChange)
{
item.BtnColor = Color.Red.ToString();
item.OtherColor = Color.Blue.ToString();
}
(You can inline the query into the foreach statement itself, but personally I find the above approach using a separate local variable clearer.)

how to filter entity type framework object by its child object value properties?

I have an entity framework object called batch, this object has a 1 to many relationship to items.
so 1 batch has many items. and each item has many issues.
I want to filter the for batch items that have a certain issue code (x.code == issueNo).
I have written the following but Im getting this error:
items = batch.Select(b => b.Items
.Where(i => i.ItemOrganisations
.Select(o => o
.Issues.Select(x => x.Code == issueNo))));
Error 1:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<System.Collections.Generic.IEnumerable<bool>>' to 'bool'
Error 2:
Cannot convert lambda expression to delegate type 'System.Func<Ebiquity.Reputation.Neptune.Model.Item,bool>' because some of the return types in the block are not implicitly convertible to the delegate return type
Select extension method needs a lambda expression that returns a boolean, but the inner o.Issues.Select returns an IEnumerable of boolean to the outer Select(o => o which result in the exception you're getting.
Try using Any instead which verifies that at least one element verifies the condition:
items = batch.Select(
b => b.Items.Where(
i => i.ItemOrganisations.Any(
o => o.Issues.Any(x => x.Code == issueNo)
)
)
);
If I understand correctly, you're trying to select through multiple layers of enumerables. In those cases you need SelectMany which flattens out the layers, not Select. LINQ's syntax sugar is made specifically to make SelectMany easier to reason about:
var items = from item in batch.Items
from org in item.ItemOrganizations
from issue in org.Issues
where issue.Code == issueNo
select item;
The compiler translates that into something like this:
var items = batch.Items
.SelectMany(item => item.ItemOrganizations, (item, org) => new {item, org})
.SelectMany(#t => #t.org.Issues, (#t, issue) => new {#t, issue})
.Where(#t => #t.issue.Code == issueNo)
.Select(#t => #t.#t.item);
You can always wrap this in a Distinct if you need to avoid duplicate items:
var items = (from item in batch.Items
from org in item.ItemOrganizations
from issue in org.Issues
where issue.Code == issueNo
select item).Distinct();
It's hard to tell what you're trying to do based on your code but I think you're looking for something like this;
var issue = batch.Select(b => b.Items).Select(i => i.Issues).Where(x => x.Code == issueNo).Select(x => x).FirstOrDefault();
The above query will return the first issue where the Issues Code property is equal to issueNo. If no such issue exists it will return null.
One problem (the cause of your first error) in your query is that you're using select like it's a where clause at the end of your query. Select is used to project an argument, when you do Select(x => x.Code == issueNo) what you're doing is projecting x.Code to a bool, the value returned by that select is the result of x.Code == issueNo, it seems like you want that condition in a where clause and then you want to return the issue which satisfies it which is what my query is doing.
items = from b in batch.Include("Items")
where b.Items.Any(x=>x.Code==issueNo)
select b;
You're getting lost in lambdas. Your LINQ chains are all embedded in each other, making it harder to reason about. I'd recommend some helper functions here:
static bool HasIssueWithCode(this ItemOrganization org, int issueNo)
{
return org.Issues.Any(issue => issue.Code == issueNo);
}
static bool HasIssueWithCode(this Item items, int issueNo)
{
return items.ItemOrganizations.Any(org => org.HasIssueWithCode(issueNo));
}
Then your answer is simply and obviously
var items = batch.Items.Where(item => item.HasIssueWithCode(issueNo));
If you inline these functions, the result is the exact same as manji's (so give manji credit for the correct answer), but I think it's a bit easier to read.

System.Func passed in to a linq where method without enumerating

I have a method where I am trying to return all default customer addresses with the matching gender. I would like to be able to build up the filtering query bit by bit by passing in System.Func methods to the where clause.
var emailAddresses = new List<string>();
// get all customers.
IQueryable<Customer> customersQ = base.GetAllQueryable(appContext).Where(o => o.Deleted == false);
// for each customer filter, filter the query.
var genders = new List<string>() { "C" };
Func<Customer, bool> customerGender = (o => genders.Contains(o.Addresses.FirstOrDefault(a => a.IsDefaultAddress).Gender));
customersQ = customersQ.Where(customerGender).AsQueryable();
emailAddresses = (from c in customersQ
select c.Email).Distinct().ToList();
return emailAddresses;
But this method calls the database for every address (8000) times which is very slow.
however if I replace the two lines
Func<Customer, bool> customerGender = (o => genders.Contains(o.Addresses.FirstOrDefault(a => a.IsDefaultAddress).Gender));
customersQ = customersQ.Where(customerGender).AsQueryable();
with one line
customersQ = customersQ.Where(o => genders.Contains(o.Addresses.FirstOrDefault(a => a.IsDefaultAddress).Gender)).AsQueryable();
Then the query only makes one call to the database and is very fast.
My question is why does this make a difference? How can I make the first method work with only calling the database once?
Use expression instead of Func:
Expression<Func<Customer, bool>> customerGender = (o =>
genders.Contains(o.Addresses.FirstOrDefault(a => a.IsDefaultAddress).Gender));
customersQ = customersQ.Where(customerGender).AsQueryable();
When you are using simple Func delegate, then Where extension of Enumerable is called. Thus all data goes into memory, where it is enumerated and lambda is executed for each entity. And you have many calls to database.
On the other hand, when you are using expression, then Where extension of Queryable is called, and expression is converted into SQL query. That's why you have single query in second case (if you use in-place lambda it is converted into expression).

Return a List with multiple results using LINQ and EF

I have this simple method that returns a user:
User usr = ReliableExecution.RetryWithExpression<User, User>(u => u.FirstOrDefault(x => x.UserEmail == userEmail));
Now I need to create a similar method, but I need to return a list
List<Asset> lst = ReliableExecution.RetryWithExpression<Asset, List<Asset>>(u => u.SelectMany(x => x.EventId == eventId));
My problem is with the [SelectMany(x => x.EventId == eventId)] part that doesn't compile and I can't understand exactly how to use LINQ to get multiple results.
I have specified "SelectMany" just for an example, it can be whatever you find correct.
This is the signature of RetryWithExpression for reference:
public static TValue RetryWithExpression<T, TValue>(Func<ObjectSet<T>, TValue> func, Int32 retryInfiniteLoopGuard = 0)
where T : class
I think your expression should be rewritten as follows:
List<Asset> lst = ReliableExecution
.RetryWithExpression<Asset, List<Asset>>(
u => u.Where(x => x.EventId == eventId).ToList()
);
In simple terms, SelectMany flattens a "list of lists of items A" into a "list of items B" using a functor that extracts a list of items B from each single item A; this is not what you want to do.
It seems like you want:
List<Asset> lst = ReliableExecution.RetryWithExpression<Asset, List<Asset>>
(u => u.SelectMany(x => x.Where(y => y.EventId == eventId)));
SelectMany expects the passed Func to return an IEnumerable, which it then flattens. You were passing through a list of Asset and then trying to select the EventId directly on the list. What you really wanted was to select all Assets in the list with matching EventId, hence the extra Where clause.

Categories