How can i make this Linq Statement more sophisticated - linq

After many hours of research i still can't get this query to shorten, but surely there is a way... My current query looks like so
decimal Cost = db.U_MBP
.Where(w => w.Model == ModelNumber)
.Select(s => s.Cost ?? 0)
.FirstOrDefault();
double Margin = db.U_MBP
.Where(w => w.Model == ModelNumber)
.Select(s => s.Margin ?? 0)
.FirstOrDefault();
decimal BasePrice = Cost / (decimal)Margin;
What I was initially trying to accomplish was an all in 1 linq statement, something like
decimal BasePrice = db.U_MBP
.Where(w => w.Model == ModelNumber)
.Select(s => s.Cost ?? 0 / (decimal)s.Margin ?? 0)
.FirstOrDefault();
But i cannot get over the cast error.
Casting to Decimal is not supported in LINQ to Entities queries, because the required precision and scale information cannot be inferred.
Can any of you help?
Answer with D's Help
decimal BasePrice = (decimal)(db.Utilities_ModelsBasePrices
.Where(w => w.Model == ModelNumber)
.Select(s => (s.Margin == null || s.Margin == 0) ? (double)0m :
((double)s.Cost) / (double)s.Margin)
.FirstOrDefault());

The problem is here:
.Select(s => s.Cost ?? 0 / (decimal)s.Margin ?? 0)
The ?? operator has lower precedence than the / operator, so it's effectively calculating:
.Select(s => s.Cost ?? (0 / (decimal)s.Margin) ?? 0)
which translates to
if (s.Cost == null)
if((0 / (decimal)s.Margin))== null
return 0;
else
return (0 / (decimal)s.Margin);
else
return s.Cost;
You need to add parens when using ?? and other operators:
decimal BasePrice = db.U_MBP
.Where(w => w.Model == ModelNumber)
.Select(s => (s.Cost ?? 0) / ((decimal)s.Margin ?? 0))
.FirstOrDefault();
However, you will get a divide-by-zero exception if s.Margin is null or 0. I would recommend:
decimal BasePrice = (decimal)(db.U_MBP
.Where(w => w.Model == ModelNumber)
.Select(s => (s.Margin == null || s.Margin == 0) ? 0.0 :
((double)s.Cost ?? 0) / s.Margin)
.FirstOrDefault());
Eric Lippert recently posted about the null coalescing precedence on his blog.

Related

Left outer join with eagar loading

I am writing a linq having a left outer join with conditions on right side table.
my code
var leftHotelRooms = db.HotelRooms.Include(hr=>hr.HotelRoomBlackoutPeriods)
.Where(room => hotelCodes.Contains(room.UserHotelId) &&
room.Scope == scope &&
room.IsDeleted == false &&
room.IsEnabled == true &&
room.MaxCapacity >= minimumNumberOfGuests)
.GroupJoin(db.HotelRoomBlackoutPeriods,
room => room.Id,
blackoutPeriod => blackoutPeriod.HotelRoomId,
(room, blackoutPeriod) => new
{
room,
blackoutPeriods = blackoutPeriod.DefaultIfEmpty()
})
.Select(a => new {a.room,a.blackoutPeriods})
.Where(
x => x.blackoutPeriods.Any(y => DbFunctions.DiffDays(y.StartDate, checkIn) != 0
&& (DbFunctions.DiffDays(y.StartDate, checkIn) < 0) ? (DbFunctions.DiffDays(y.StartDate, checkOut) >= 0 ? false : true) : true &&
(DbFunctions.DiffDays(y.StartDate, checkIn) > 0) ? (DbFunctions.DiffDays(y.EndDate, checkIn) <= 0 ? false : true) : true
))
.ToList();
}
which works perfectly fine but it does not eagerly load, when i convert result of above query to my business model it again fires some queries on db.HotelRoomBlackoutPeriods
Please provide the optimum way to achieve this.
thanks in advance.
sorry (if i am asking something nonsense ) in advance
Eager loading does not work when you change the shape of the result set. Shape of your result set is HotelRoom type. Once you use any manual join or projection, you are changing the result set and eager loading request is ignored.

Why is Linq that slow (see provided examples)

This Linq is very slow:
IEnumerable<string> iedrDataRecordIDs = dt1.AsEnumerable()
.Where(x => x.Field<string>(InputDataSet.Column_Arguments_Name) == sArgumentName
&& x.Field<string>(InputDataSet.Column_Arguments_Value) == sArgumentValue)
.Select(x => x.Field<string>(InputDataSet.Column_Arguments_RecordID));
IEnumerable<string> iedrDataRecordIDs_Filtered = dt2.AsEnumerable()
.Where(x => iedrDataRecordIDs.Contains(
x.Field<string>(InputDataSet.Column_DataRecordFields_RecordID))
&& x.Field<string>(InputDataSet.Column_DataRecordFields_Field)
== sDataRecordFieldField
&& x.Field<string>(InputDataSet.Column_DataRecordFields_Value)
== sDataRecordFieldValue)
.Select(x => x.Field<string>(InputDataSet.Column_DataRecordFields_RecordID));
IEnumerable<string> ieValue = dt2.AsEnumerable()
.Where(x => x.Field<string>(InputDataSet.Column_DataRecordFields_RecordID)
== iedrDataRecordIDs_Filtered.FirstOrDefault()
&& x.Field<string>(InputDataSet.Column_DataRecordFields_Field) == sFieldName)
.Select(x => x.Field<string>(InputDataSet.Column_DataRecordFields_Value));
if (!ieValue.Any()) //very slow at this point
return iedrDataRecordIDs_Filtered.FirstOrDefault();
This change accelerates it by a factor of 10 or more
string sRecordID = dt2.AsEnumerable()
.Where(x => iedrDataRecordIDs.Contains(
x.Field<string>(InputDataSet.Column_DataRecordFields_RecordID))
&& x.Field<string>(InputDataSet.Column_DataRecordFields_Field)
== sDataRecordFieldField
&& x.Field<string>(InputDataSet.Column_DataRecordFields_Value)
== sDataRecordFieldValue)
.Select(x => x.Field<string>(InputDataSet.Column_DataRecordFields_RecordID))
.FirstOrDefault();
IEnumerable<string> ieValue = dt2.AsEnumerable()
.Where(x => x.Field<string>(InputDataSet.Column_DataRecordFields_RecordID) == sRecordID
&& x.Field<string>(InputDataSet.Column_DataRecordFields_Field) == sFieldName)
.Select(x => x.Field<string>(InputDataSet.Column_DataRecordFields_Value));
if (!ieValue.Any()) //very fast at this point
return iedrDataRecordIDs_Filtered.FirstOrDefault();
The only change is that I store the result directly in a new variable and use create the where clause with this value instead of a LINQ query (which should be calculated when needed). But LINQ seems to calculate it in a bad way here or am I doing something wrong?
Here some values of my data
dt1.Rows.Count 142
dt1.Columns.Count 3
dt2.Rows.Count 159
dt2.Columns.Count 3
iedrDataRecordIDs.Count() 1
iedrDataRecordIDs_Filtered.Count() 1
ieValue.Count() 1
You're asking why
IEnumerable<string> iedrDataRecordIDs_Filtered = data;
foreach (var item in collection)
{
// do something with
iedrDataRecordIDs_Filtered.FirstOrDefault();
}
is slower than
string sRecordID = data.FirstOrDefault();
foreach (var item in collection)
{
// do something with
sRecordID;
}
Very simply because you're evaluating the iedrDataRecordIDs collection every time you get the FirstOrDefault. This isn't a concrete object, it's an enumerable set. That's really just a function that returns some objects. Every time you query it the function will be called and you'll pay that execution cost.
If you change
IEnumerable<string> iedrDataRecordIDs_Filtered = dt2.AsEnumerable()...
var recordIDs = iedrDataRecordIDs_Filtered.ToList();
and then use recordIDs.FirstOrDefault() you'll see a huge performance increase.

Exception when using LINQ SUM

I'm trying to get the SUM of "bookings" and I get error "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."
var bookings = entities.Bookings.Where(x => x.ID == id &&
x.StartDate <= bookingEnd &&
x.EndDate >= bookingStart)
.Sum(x => x.BookingQuantity);
How shall I fix this? I need to get 0 if it ever gets to be null else its bookings.
Try the null coalescing operator:
var bookings = entities.Bookings.Where(x => x.ID == id &&
x.StartDate <= bookingEnd &&
x.EndDate >= bookingStart &&
x.BookingQuantity != null)
.Sum(x => (int?)x.BookingQuantity) ?? 0;
or declare bookings as a nullable int
int? bookings = ...
The compilers type inference is picking up the result of Sum as a plain int, which should never be null.
This page suggests a fix to this problem;
Sum(x => (int?)x.BookingQuantity) ?? 0;
Add checking for null.
var bookings = entities.Bookings.Where(x => x.ID == id &&
x.StartDate <= bookingEnd &&
x.EndDate >= bookingStart &&
x.BookingQuantity != null)
.Sum(x => x.BookingQuantity);

Enhance this LINQ query for readability and performance?

I'm not the greatest with LINQ, but I'm trying to retrieve all the ModuleAvailabilities where the academicYear is the current year.
Are there any improvements to be made here?
pathway.PathwayFoundationModule.Attach(
pathway.PathwayFoundationModule.CreateSourceQuery()
.Include("Module")
.Include("Module.ModuleAvailabilities.Location")
.Where(o => o.Module.ModuleAvailabilities
.Where(x => x.AcademicYear == academicYear.Current)
.Count() >= 0)
);
I think you mean
pathway.PathwayFoundationModule.Attach(
pathway.PathwayFoundationModule.CreateSourceQuery()
.Include("Module")
.Include("Module.ModuleAvailabilities.Location")
.Where(o => o.Module.ModuleAvailabilities
.Any(x => x.AcademicYear == academicYear.Current));

Linq error generic parameter or the query must use a nullable type

I got this error when i use sum function in LINQ:
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.
GroupProduct.Where(a => a.Product.ProductID==1).Sum(Content => Content.Amount==null?0:Content.Amount),
This is what I usually use. This will cover the possibility of Amount being null and also cover the possibility of an empty set.
GroupProduct.Where(a => a.Product.ProductID == 1)
.Select(c => c.Amount ?? 0) // select only the amount field
.DefaultIfEmpty() // if selection result is empty, return the default value
.Sum(c => c)
DefaultIfEmpty() returns the default value associated with Amount's type, which is int, in which case the default value is 0.
Did you try the following:
GroupProduct.Where(a => a.Product.ProductID==1).Sum(Content => (decimal?)Content.Amount)
The code from my application looks like:
var data = query.AsEnumerable().Select(row => TaskInfo.FetchTaskInfo(row,
ctx.ObjectContext.Hours.Where(hour => hour.TaskId == row.TaskId).Sum(hour => (decimal?)hour.Duration),
ctx.ObjectContext.Notes.Count(note => note.SourceType == (int)SourceType.Task && note.SourceId == row.TaskId)));
You could exclude at source?
var sum = GroupProduct.Where(a => a.Product.ProductID==1 && a.Amount != null)
.Sum(a => (decimal)a.Amount);
Try this:
var sum = GroupProduct.Where(a => a.Product.ProductID==1).Sum(Content => (int?) Content.Amount);
sum = sum ?? 0;
This looks like it should work (and usually does) but fails when the Where() method returns null:
decimal sum1 = GroupProduct
.Where(a => a.Product.ProductID == 1)
.Sum(c => c.Amount ?? 0);
The error: "The cast to value type 'Decimal' failed because the materialized value is null" is due to the Sum() method returning null (not zero) when summing over an empty set.
Either of these work for me:
decimal? sum2 = GroupProduct
.Where(a => a.Product.ProductID == 1)
.Sum(c => c.Amount);
decimal sum3 = GroupProduct
.Where(a => a.Product.ProductID == 1)
.Sum(c => c.Amount) ?? 0;

Resources