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
Related
I have a User table, which may have a related (FK) record in Address.
So in LINQ, I am trying to:
var data = _context.User.Where(x=>x.Deleted.HasValue == false)
.Select(y=> new MyObject {
Id = y.Id,
Name = y.Name,
Address = y.Address != null ? y.Address.Description : null
});
But as soon as I add the ternary operator (As I cannot reference y.Address.Description if there is no record there), my Select fails with a design-time error: Ambiguous Invocation.
What's the correct way to do this? In this case, there may be no 'Address' record.
Design time error on the 'select':
I think you need to cast the null to a string:
var data = _context.User.Where(x=>x.Deleted.HasValue == false)
.Select(y=> new MyObject {
Id = y.Id,
Name = y.Name,
Address = y.Address != null ? y.Address.Description : (string) null
});
I assume here that Description is of type string.
You could also try the Null-conditional operator:
var data = _context.User.Where(x=>x.Deleted.HasValue == false)
.Select(y=> new MyObject {
Id = y.Id,
Name = y.Name,
Address = y.Address?.Description
});
The problem has to do with type inference. The compiler cannot determine the result type of the ternary operator, thus it is necessary to explicitly indicate the type of the third operand (the null) by casting it.
See related question: Understanding C# compilation error with ternary operator
Simple example to cause this error:
int? i = true ? 1 : null;
On Visual Studio 2019 / .Net Framework 4.7.2, I get the following error:
Type of conditional expression cannot be determined because there is no implicit conversion between 'int' and '<null>'
Correction:
int? i = true ? 1 : (int?) null;
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
This is my current LINQ statement:
var results = from l in leads
select new MyObject
{
LeadID = l.LeadID,
SelectedProposalEngineerID = l.LeadContacts.Where(contact => contact.LeadContactTypeID == LeadContactType.ProposalEngineer).FirstOrDefault().ContactID
};
The trouble I'm having is that the last item is often null. So when I try to convert "results" to a List, I get
{"The cast to value type 'System.Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type."}
I don't want to make SelectedProposalEngineerID a nullable int, for problems that would cause down stream. How would I give it a value of 0 when it's null?
I have seen a LOT of other threads about this, but I can't seem to adapt any of their answers to this case.
Use DefaultIfEmpty extension method.
var results = from l in leads
select new MyObject
{
LeadID = l.LeadID,
SelectedProposalEngineerID =
l.LeadContacts.Where(contact => contact.LeadContactTypeID == LeadContactType.ProposalEngineer)
.Select(contact => contact.ContactID)
.DefaultIfEmpty(0)
.FirstOrDefault()
};
Nullable<int> ID;
var results = from l in leads
select new MyObject
{
LeadID = l.LeadID,
SelectedProposalEngineerID = (ID = l.LeadContacts.Where(contact => contact.LeadContactTypeID == LeadContactType.ProposalEngineer).FirstOrDefault().ContactID).HasValue ? ID.Value : 0;
};
A ternary operator should do the job. Assign the result to a variable, then if it's not null, cast the variable to int and return it, else return 0.
I have this code :
var res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new { val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString() })
.FirstOrDefault();
however , since this returns an anonymous type , its default value is null .
I want it to act as FirstOrDefault for int type.
so if there is no record , it will return 0 ( default behavior as int).
is it possible ?
p.s. ( of course i can check it in a condition but still , i prefer the linq way).
Return an anonymous type that signifies "nothing" and either use the null coalescing operator:
var res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new { val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString() })
.FirstOrDefault() ?? new { val = 0, idpolicy = "" };
Or the DefaultIfEmpty extension method:
var res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new { val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString() })
.DefaultIfEmpty(new { val = 0, idpolicy = "" })
.FirstOrDefault();
You would only be able to return an int in place of an anonymous type if you in fact return an object and cast later on (as per #recursive's answer), but this to me seems counter-productive.
FirstOrDefault does not offer a way to specify what "default" is.
You can't have an expression evaluate to 2 different data types on two execution paths.
Also, even if that's possible, var is different to dynamic so the variable type won't be inferred by the compiler.
Doing this doesn't make any sense, and I would encourage you to think about why you want to do this, and find some cleaner, more direct way to accomplish it.
With that said, here's a small tweak to #IronicMuffin's approach that will actually work.
object res1 = dtData.AsEnumerable()
.Where(...)
.Select(f => new {
val = f["PremiumAfterUWDiscount"].ToDecimalOrZero(),
idpolicy = f["IdPolicy"].ToString()
})
.FirstOrDefault() as object ?? 0;
I don't think this is very useful though.
I have following Linq:
var ownerRegistryId = 731752693037116688;
var excludeTypes = new[]
{
"CA00", "CA01", "CA03", "CA04", "CA02",
"PA00", "PA01", "PA02", "PA03", "PA04"
};
var maxStateChangeMonth = 4;
var excludeStatusId = 999;
var includeMortgage = new[] { "CL10", "CL11", "PL10", "PL11" };
var sum = (
from account in context.Accounts
from owner in account.AccountOwners
where owner.AccountOwnerRegistryId == ownerRegistryId
where !excludeTypes.Contains(account.AccountType)
where account.StateChangeDate == null ||
(account.StateChangeDate.Month - DateTime.Now.Month)
<= maxStateChangeMonth
where includeMortgage.Contains(account.AccountType) ||
account.AccountType.Contains("Mortgage")
where account.AccountStatusId != excludeStatusId
select account.MinimumInstallment)
.Sum(minimumInstallment => Math.Abs(minimumInstallment));
but I get the error:
The cast to value type 'Decimal'
failed because the materialized value
is null. Either the result type's
generic parameter or the query must
use a nullable type.
this error comes as soon as I add this:
where (includeMortgage.Contains(account.AccountType) ||
account.AccountType.Contains("Mortgage"))
If I remove this from above query, it works.
The query is translation of following SQL:
SELECT Sum(ABS([Minimum Installment])) AS SumOfMonthlyPayments FROM tblAccount
INNER JOIN tblAccountOwner ON tblAccount.[Creditor Registry ID] = tblAccountOwner.
[Creditor Registry ID] AND tblAccount.[Account No] = tblAccountOwner.[Account No]
WHERE (tblAccountOwner.[Account Owner Registry ID] = 731752693037116688)
AND (tblAccount.[Account Type] NOT IN
('CA00', 'CA01', 'CA03', 'CA04', 'CA02', 'PA00', 'PA01', 'PA02', 'PA03', 'PA04'))
AND (DATEDIFF(mm, tblAccount.[State Change Date], GETDATE()) <=
4 OR tblAccount.[State Change Date] IS NULL AND ((tblAccount.[Account Type] IN ('CL10','CL11','PL10','PL11')) OR
tblAccount.[Account Type] LIKE 'Mortgage')) AND (tblAccount.[Account Status ID] <> 999)
I'd try to rewrite the last two lines of your query like so:
var sum = (
...
select account)
.Sum(a => Math.Abs(a.MinimumInstallment));
That's how I interprete this part of the exception "...or the query must use a nullable type". By using the projection select account.MinimumInstallment you have a non-nullable type, namely decimal which is the type of account.MinimumInstallment.
Not sure though, just a guess.
Edit
The problem might actually be the final assignment var sum = .... Since you don't specify the result type explicitely the compiler will here infer the type to decimal because MinimumInstallment is decimal. The query can actually return null when the selected recordset was empty so the cast to decimal is impossible.
So, let's help the compiler to infer the result type of the query to decimal?:
var sum = (decimal?)(from ... ) ?? 0;
(Replace from ... by your original query or maybe by my modified version above.)
Edit 2
OK, the first Edit didn't work (according to comment in another question). Indeed I could reproduce the issue in a similar example. But the following worked in my example:
var sum = (
...
select account)
.Sum(a => (decimal?)Math.Abs(a.MinimumInstallment))
.GetDefaultOrValue();
Try using:
Math.Abs((decimal)(minimumInstallment.HasValue ? minimumInstallment : 0));
How about:
Math.Abs((decimal)(minimumInstallment!= null ? minimumInstallment : 0));