How to concat multiple selects with LINQ? - c#

I have main select which looks like this:
protected IQueryable<Answers> GetActualAnswers<TAns>(DateTime? start, DateTime? end, long? statusId) where TAns: AnswersBase
{
_contex.Set<TAns>.Where(x => x.Type == VoteType.Good)
.Select(vv => new Answers
{
CreatedAt = vv.CreatedAt,
StatusId = vv.StatusId,
Type = vv.Type ,
AnswerInGuideStatusId = vv.AnswerInGuideStatusId
}
}
I'm using this method in two simple queries:
var result1 = GetActualAnswers<JournalAnswers>(start, end, statusId)
.Select(j => new UnitedAnswers
{
Question = j.Question,
}
var result2 = GetActualAnswers<BoAnswers>(start, end, statusId)
.Select(b => new UnitedAnswers
{
Prospects = b.Prospects ,
}
var mainResult = result1.Concat(result2);
I get errors:
Sql = Sql = '((System.Data.Entity.Infrastructure.DbQuery<UnitedAnswers>)result1).Sql' threw an exception of type 'System.NotSupportedException'
Sql = Sql = '((System.Data.Entity.Infrastructure.DbQuery<UnitedAnswers>)result2).Sql' threw an exception of type 'System.NotSupportedException'
Sql = Sql = '((System.Data.Entity.Infrastructure.DbQuery<UnitedAnswers>)mainResult).Sql' threw an exception of type 'System.NotSupportedException'
Is it possible to use several Selects? May be someone can give advice with this query?

Firstly, I was wondering where the property j.Question and b.Prospects you get, While in the method GetActualAnswers you did not get the value of 2 properties above.
Secondly, At the method GetActualAnswers you were returning IQueryable, so you should check empty instead of null value
Then your case might look like this
var mainResult = Enumerable.Concat(
resut1 ?? Enumerable.Empty<UnitedAnswers>(),
resut2 ?? Enumerable.Empty<UnitedAnswers>()
or
var mainResult = Enumerable.Concat(
result1.AsEnumerable(),
result2.AsEnumerable());
The following links are useful for you.
How to merge two IQueryable lists
Enumerable.AsEnumerable

Related

NotSupportedException when converting an IQueryable<object>

When I do this :
var db = new NotentoolEntities();
IQueryable<GroupOfBranches> queryGOB = db.tabGroupOfBranches.Cast<GroupOfBranches>().Where(x => x.intGroupID.Equals(ID));
List<GroupOfBranches> GOB = new List<GroupOfBranches>(queryGOB); //here is the error
I've got the following error :
A first chance exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll
I suppose that the underlying LINQ provider cannot convert call to Equals to anything it knows about.
Use == operator instead. That will end up in a different expression tree, which can be translated.
var db = new NotentoolEntities();
IQueryable<GroupOfBranches> queryGOB =
db.tabGroupOfBranches.Cast<GroupOfBranches>().Where(x => x.intGroupID == ID));
List<GroupOfBranches> GOB = new List<GroupOfBranches>(queryGOB);
If GroupOfBranches is derived class from whatever the type is returned by tabGroupOfBranches, then you should probably use OfType, rather than Cast.
Otherwise, Cast might go after Where, and be replaced with a Select which creates instances of GroupOfBranches class explicitly.
Might consider to change your code to
using (var db = new NotentoolEntities())
{
var GOB = db.tabGroupOfBranches.Where(x => x.intGroupID == ID).Select(y => new GroupOfBranches
{
/// here specify your fields
}).AsNoTracking().ToList();
}

Linq - EntityFramework NotSupportedException

I have a query that looks like this:
var caseList = (from x in context.Cases
where allowedCaseIds.Contains(x => x.CaseId)
select new Case {
CaseId = x.CaseId,
NotifierId = x.NotifierId,
Notifier = x.NotifierId.HasValue ? new Notifier { Name = x.Notifier.Name } : null // This line throws exception
}).ToList();
A Case class can have 0..1 Notifier
The query above will result in the following System.NotSupportedException:
Unable to create a null constant value of type 'Models.Notifier'. Only entity types, enumeration types or primitive types are supported
in this context.
At the moment the only workaround I found is to loop the query result afterwards and manually populate Notifierlike this:
foreach (var c in caseList.Where(x => x.NotifierId.HasValue)
{
c.Notifier = (from x in context.Notifiers
where x.CaseId == c.CaseId
select new Notifier {
Name = x.Name
}).FirstOrDefault();
}
But I really don't want to do this because in my actual scenario it would generate hundreds of additional queries.
Is there any possible solution for a situation like this?.
I think you need to do that in two steps. First you can fetch only the data what you need with an anonymous type in a single query:
var caseList = (from x in context.Cases
where allowedCaseIds.Contains(x => x.CaseId)
select new {
CaseId = x.CaseId,
NotifierId = x.NotifierId,
NotifierName = x.Notifier.Name
}).ToList();
After that, you can work in memory:
List<Case> cases = new List<Case>();
foreach (var c in caseList)
{
var case = new Case();
case.CaseId = c.CaseId;
case.NotifierId = c.NotifierId;
case.NotifierName = c.NotifierId.HasValue ? c.NotifierName : null;
cases.Add(case);
}
You could try writing your query as a chain of function calls rather than a query expression, then put an .AsEnumerable() in between:
var caseList = context.Clases
.Where(x => allowedCaseIds.Contains(x.CaseId))
.AsEnumerable() // Switch context
.Select(x => new Case() {
CaseId = x.CaseId,
NotifierId = x.NotifierId,
Notifier = x.NotifierId.HasValue
? new Notifier() { Name = x.Notifier.Name }
: null
})
.ToList();
This will cause EF to generate an SQL query only up to the point where you put the .AsEnumerable(), further down the road, LINQ to Objects will do all the work. This has the advantage that you can use code that cannot be translated to SQL and should not require a lot of changes to your existing code base (unless you're using a lot of let expressions...)

Why is LINQ expression "Not Supported" when converting a Char to String inside of it?

If I make conversion of firstLetterOfLastName to string by adding empty string "" inside of expression I get the following exception when try to convert result to List:
A first chance exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll
Code which gives trouble:
public ActionResult Index(char firstLetterOfLastName = 'A') {
var queryResult = db.Persons.Where(person => person.LastName.StartsWith(firstLetterOfLastName + ""))
.OrderBy(person => person.Id);
var list = queryResult.ToList(); // EXCEPTION
return View(list);
}
However, when I make conversion earlier outside of the LINQ expression like here:
public ActionResult Index(char firstLetterOfLastName = 'A') {
string flofnAsString = firstLetterOfLastName + "";
var queryResult = db.Persons.Where(person => person.LastName.StartsWith(flofnAsString + ""))
.OrderBy(person => person.Id);
var list = queryResult.ToList();
return View(list);
}
there is no problem. Why is so?
The error tells you exactly why. The query provider doesn't know how to map that operation into SQL. It does know what to do with the result of that operation when you executed it in your application and evaluated it to its value.

Passing parameter to LINQ query

I have a method like below:
public void GetUserIdByCode(string userCode)
{
var query = from u in db.Users
where u.Code == userCode // userCode = "LRAZAK"
select u.Id;
var userId = query.FirstOrDefault(); // userId = 0 :(
}
When I ran the code, I got the default value of 0 assigned to userId meaning the Id was not found.
However, if I changed the userCode with a string like below, I will get the value I want.
public void GetUserIdByCode(string userCode)
{
var query = from u in db.Users
where u.Code == "LRAZAK" // Hard-coded string into the query
select u.Id;
var userId = query.FirstOrDefault(); // userId = 123 Happy days!!
}
My question is why passing the parameter into the LINQ query does not work?
When I stepped into the code, I got the SQL statement like so:
// Does not work...
{SELECT "Extent1"."LOGONNO" AS "LOGONNO"FROM "DEBTORSLIVE"."DEBTORS_LOGONS" "Extent1"WHERE ("Extent1"."LOGONCODE" = :p__linq__0)}
The hard-coded LINQ query (the working one) gives an SQL statement as below:
// Working just fine
{SELECT "Extent1"."LOGONNO" AS "LOGONNO"FROM "DEBTORSLIVE"."DEBTORS_LOGONS" "Extent1"WHERE ('LRAZAK' = "Extent1"."LOGONCODE")}
What would be the solution?
As a work-around, I use Dynamic Linq.
The code below is working for me.
public void GetUserIdByCode(string userCode)
{
string clause = String.Format("Code=\"{0}\"", userCode);
var userId = db.Users
.Where(clause)
.Select(u => u.Id)
.FirstOrDefault();
}
The database query returns an object of User with Code and Id as properties. This is defined in one of my classes.
Here is syntax that will work to pass an argument to a LINQ query.
Not sure how many people will be searching this topic so many years later, but here's a code example that gets the job done:
string cuties = "777";
// string collection
IList<string> stringList = new List<string>() {
"eg. 1",
"ie LAMBDA",
"777"
};
var result = from s in stringList
where (s == cuties)
select s;
foreach (var str in result)
{
Console.WriteLine(str); // Output: "777"
}

Problem with LINQ to Entities query using Sum on child object property

Given this query:
from s in services
select new
{
s.Id,
s.DateTime,
Class = s.Class.Name,
s.Location,
s.Price,
HeadCount = s.Reservations.Sum(r => r.PartySize), // problem here. r.PartySize is int
s.MaxSeats
}
If the service doesn't have any reservations, this exception is thrown:
System.InvalidOperationException: The cast to value type 'Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
I get it, but how should I deal with it? My intention is if there are no reservations, then HeadCount be assigned 0.
There's an even simpler solution:
from s in services
select new
{
s.Id,
s.DateTime,
Class = s.Class.Name,
s.Location,
s.Price,
HeadCount = (int?)s.Reservations.Sum(r => r.PartySize),
s.MaxSeats
}
Note the cast. This may also produce simpler SQL than #Ahmad's suggestion.
Essentially, you're just helping out type inference.
You should check for it:
HeadCount = s.Reservations != null ? s.Reservations.Sum(r => r.PartySize) : 0,
This should resolve your problem:
Try to cost the int to int?
from s in services
select new
{
s.Id,
s.DateTime,
Class = s.Class.Name,
s.Location,
s.Price,
HeadCount = s.Reservations.Sum(r => (int?) r.PartySize),
s.MaxSeats
};
HeadCount = HeadCount ?? 0;
A simple ternary operator should fix the problem nicely...
something like this:
HeadCount = (s.Reservations != null && s.Reservations.Any()) ? s.Reservations.Sum(r => r.PartySize) : 0;
This will handle for both null and empty situations

Categories