why does LastOrDefault conflicts with IEnumerable<Viewmodel> [closed] - c#

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I'm trying to merge several query results and it works by using IEnumerable<Viewmodel> as type per query and I use concat to combine but I cannot use LastOrDefault() per query - combine more than two results
But I need to specify that the query will return only the Last value and will return none if it is null , but there is an error.
I already tried First() or FirstOrDefault() or SingleOrDefault() but still it gave me an error.
EDIT:
the three queries have the same structures on the columns returned
IEnumerable<ViewModel> query1= db.Table1
.Where(er => er.ID == Filter1)
.Select(er => new ViewModel
{
//columns here
}).ToList().LastOrDefault(); //the **ToList().LastOrDefault();** shows error but I need this to get only the last row from the query if there is
IEnumerable<ViewModel> query2= db.Table2
.Where(er => er.ID == Filter2)
.Select(er => new ViewModel
{
//columns here
}).ToList().LastOrDefault(); //the **ToList().LastOrDefault();** shows error but I need this to get only the last row from the query if there is
IEnumerable<ViewModel> query3= db.Table3
.Where(er => er.ID == Filter3)
.Select(er => new ViewModel
{
//columns here
}).ToList().LastOrDefault(); //the **ToList().LastOrDefault();** shows error but I need this to get only the last row from the query if there is
var result = query1.Concat(query2).Concat(query3).ToList();//it shows error here if I add the **ToList().LastOrDefault()** per query
if I add the LastOrDefault() on the result statement it will show only one row which is from the query3
How can I fix this? Thank you so much, please be patient to me.

Well, I would say the best way is to override List<T>, but if your project is to small for this and this is the only place where you need it you could make it like this:
List<ViewModel> list1 = new List<ViewModel>();
var query1 = db.Table1.Where(er => er.ID == Filter1)
.Select(er => new ViewModel
{
//columns here
}).ToList().LastOrDefault();
list1.Add(query);
var query2 = db.Table2.Where(er => er.ID == Filter2)
.Select(er => new ViewModel
{
//columns here
}).ToList().LastOrDefault();
list1.Add(query);
var query3 = db.Table3.Where(er => er.ID == Filter3)
.Select(er => new ViewModel
{
//columns here
}).ToList().LastOrDefault();
list1.Add(query);
return list1;

There's no need to concat stuff or or forward-declaring lists and adding elements as you go etc. You can simply select the items one by one and materialize them into a list as follows:
var a = table
.Where(PredicateA)
.Select(Selector)
.FirstOrDefault();
var b = table
.Where(PredicateB)
.Select(Selector)
.FirstOrDefault();
var c = table
.Where(PredicateC)
.Select(Selector)
.FirstOrDefault();
var result = new[] {a, b, c};
Or you can select them all together in a single query (probably better performance-wise, but favor what you think is more readable).
var result = table
.Where(x => PredicateA(x) || PredicateB(x) || PredicateC(x))
.Select(Selector);

LastOrDefault gives you not an IEnumerable but only one element. And you try to assign it to IEnumerable. That is your problem. In your case I would rewrite LastOrDefault (or use another function instead of it) so it returns a IEnumerable with only one element you need.

Related

How to dynamically add Where and Or statements to a Linq query [duplicate]

This question already has answers here:
Dynamic where clause (OR) in Linq to Entities
(2 answers)
Closed 3 years ago.
First time using c# and Linq. I have a string coming in through my route. I want to search a couple of different columns for the values in my string. Assuming I'm splitting each word on a space, foreach one of these items I want to dynamically add a .Where to my linq statement. I'm thinking I may need to dynamically add an .Or as well.
foreach (string q in query)
{
results = results.Where(u => u.Name.Contains(r));
results = results.Where(u => u.Text.Contains(r));
}
I'm used to JS where you could do something like results += results.Where(...) I'm not sure the appropriate way to structure this kind of thing using linq.
edit: here is the entire method for clarity
using (var context = new MessageContext())
{
string[] words = query.Split(" ");
var messages = (from m in context.Messages
join u in context.Users on m.UserId equals u.UserID
select new
{
m.Id,
m.Date,
m.Name,
m.Text,
m.UserId,
u.Image
});
foreach (string word in words)
{
messages = messages.Where(u => u.Name.Contains(word)).Union(messages.Where(u => u.Text.Contains(word)));
return messages.ToList();
}
Linq uses lazy evaluation (the results are not evaluated until you start to iterate over the results, or until you call a method like ToList()). As John pointed out each successive call is really just modifiying the search criteria. therefore in your example
results = results.Where(u => u.Name.Contains(r));
results = results.Where(u => u.Text.Contains(r));
is equivalent to
results = results.Where(u => u.Name.Contains(r)).Where(u => u.Text.Contains(r));
which implies a AND condition. If you want an OR condition you would need to us the Union operator.
results = results.Where(u => u.Name.Contains(r)).Union(results.Where(u => u.Text.Contains(r)));
The benefit of this lazy evaluation is that you can extract a base query and add on additional search criteria, thus simplifying your code.
I hope this helps.
foreach (string q in query)
{
results = results.Where(u => u.Name.Contains(r) || u.Text.Contains(r));
}
Or may be you need to elaborate your question a bit more

Sum of child of child in LINQ

I need help in LINQ query in EF6.
the master table is called EXAM. its child is ExamResult.
each ExamResult has a Question and the selected Answer.
each Answer has power (0 if wrong, 1 if correct).
if I want to know the total of correct answers, I simply run the command:
var examTotal = db.Exams.FirstOrDefault(ex => ex.ExamID == examId).ExamResults.Sum(er => er.Answer.Power);
my problem is when some questions were not answered, and I get NullReferenceException.
There are a couple problems with your "command".
First off, there are at least two queries:
(1) var exam = db.Exams.FirstOrDefault(ex => ex.ExamID == examId);
(2) var examTotal = exam.ExamResults.Sum(er => er.Answer.Power);
Note that the second really executes inside the LINQ to Objects context (eventually including some hidden db calls due to lazy loading).
In that context, there are 2 possible places that can raise NRE
(A) exam.ExamResults if exam is null
(B) er.Answer.Powerif er.Answer is null
You can fix them by including null checks as proposed in other answers.
But better way would be make your command execute a single query inside the LINQ to Entities context where navigation properties have different meaning:
var examTotal = db.Exams.Where(ex => ex.ExamID == examId)
.SelectMany(ex => ex.ExamResults)
.Sum(er => (int?)er.Answer.Power) ?? 0;
The only trick needed is to project the field you want to sum to a nullable type (I've used int? assuming the Power field is of int type, change it to your type if different). This is letting the EF to always return the Sum regardless of whether er.Answer is null or ex.ExamResults is empty.
Some general nullchecks should do the trick
var exam = db.Exams.FirstOrDefault(ex => ex.ExamID == examId);
var examTotal = exam.ExamResults.Sum(er => er.Answer?.Power ?? 0);
...and just in case you're not using C# 6, here's Another version of it:
var exam = db.Exams.FirstOrDefault(ex => ex.ExamID == examId);
var examTotal = exam.ExamResults.Sum(er => er.Answer != null ? er.Answer.Power : 0);
Try this:
var examTotal = db.Exams.FirstOrDefault(ex => ex.ExamID == examId).ExamResults.Where(er => er.Answer != null).Sum(er => er.Answer.Power);
This should work:
var examTotal = db.Exams.FirstOrDefault(ex => ex.ExamID == examId).ExamResults.Count(er => er.Answer.Power == 1);
This will not use the value but instead see if it is equal to 1, thus not generate any NullReferenceException.

LINQ: Generate "AND" Expression instead of "OR" when using "CONTAINS"

I have this List:
string[] countries = {
"USA",
"CANADA"
};
When I run this query :
query = (from user in db where
user.Orders.Any(order => order.Price > 10 &&
countries.Contains(order.DestinationCountry)))
Output is a list of users that have Orders sent to "USA" OR "Canada".
but I want the list of users that have Orders sent to both "USA" AND" "CANADA".
I can do this using below code but i'm searching for a pure linq solution without any ForEach:
foreach (country in countries) {
query = (from user in query where
user.Orders.Any(order => order.Price > 10 &&
order.DestinationCountry == country));
}
Answers:
A. Using .Aggregate()
Generated query is just like For Each.
B.where countries.All(c => user.Orders.Any(o => o.Price > 10 && o.DestinationCountry == c))
When there is no element in Countries List (When I want all users based only on Price parameter), the result is not correct and other parameter is not considered!
Update 1:
I have tried .All() instead of .Contains() before posting and it returns 0 users.
Update 2:
I have updated my question to make it closer to the real problem.
lets say Country is not the only parameter.
Update 3:
Checked some answers and added the result to my question.
So you want a list of the users such that all the countries in the list are present in the set of order destinations?
Logically, that would be:
query = from user in db
where countries.All(c => user.Orders.Any(o => o.DestinationCountry == c))
select ...;
However, I'm not confident that EF will do what you want with that. It's not clear to me what the right SQL query would be to start with - in a simple way, at least.
query =
db.Users.Where(user =>
countries.All(country =>
user.Orders.Any(order =>
order.DestinationCountry == country)))
You can do it like this:
query = (from user in db where
user.Orders
.Where(o => countries.Contains(o.DestinationCountry))
.GroupBy(o => o.DestinationCountry)
.Count() == countries.Count
);
The idea is to keep only the orders going to countries of interest, then group by country, and check that the number of groups equals the number of countries.
It's possible using Enumerable.Aggregate:
query = countries.Aggregate(query,
(q, c) =>
from user in q
where user.Orders.Any(order => order.DestinationCountry == c)
select user);
but really, this is harder to understand than your foreach loop, so I'd just go with that.
Note that although I refer to a member of Enumerable, that member of Enumerable is actually building up an IQueryable<User> query chain just like your foreach loop, so this will not cause the filtering to move to the client.

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.

how to select an item from generic list by linq

I have a LINQ query which contains a method GetInstanceForDatabase()
principlesList.Select(p => p.GetInstanceForDatabase()).ToList()
where
List<PrincipleInstance>() principlesList = ...
// (contains list of principle like "Manual Trades", "OPM", "Flora")
GetInstanceForDatabase() is a method which takes all other info about a principle (like manual trades).
My problem is that I want to sort out only principle like only "Manual Trades".
I want to put a where clause. I tried but it is fails.
To get a single item use:
query.First(x => x.property == "Manual Trades");
// or
query.FirstOrDefault(x => x.property == "Manual Trades");
var list = p.GetInstanceForDatabase().where(x => x.propertyName == "Manual Trades").ToList();
I'm sure you're GetInstanceForDatabase needs to return your collection that you then filter for the 'Manual Trades' but I can't really tell how you get your list of PrincipalInstances from the question.
This is the correct syntax of using Where in LINQ
principlesList.Select(p => p.GetInstanceForDatabase()).Where(p => p.SomeProperty == "SomeValue").ToList();

Categories