cast to decimal in linq query - c#

var data = await dbContext.Set<OwnerData>().FromSqlRaw(#"
SELECT [OWP].[OwnerProfileId],
SELECT [OWP].[Email],
FROM [user].[OwnerProfile] AS [OWP]
CAST(ISNULL([OWB].[CustomBalance], 0) AS decimal(18, 3)) AS [CustomBalance]
INNER JOIN [user].[OwnerBalance] AS [OWB] ON [OWB].[OwnerProfileId] = [OWP].[OwnerProfileId]
WHERE [ThirdPartyRefId] = {0}", ownerProfileId)
.ToListAsync();
I rewrite this into linq expression like this
var data = await _context.Set<OwnerProfile>()
.Include(x => x.OwnerBalances)
.Where(x => x.ThirdPartyRefId== ownerProfileId)
.ToListAsync();
not sure how to set this
CAST(ISNULL([OWB].[CustomBalance], 0) AS decimal(18, 3)) AS [CustomBalance]
into lambda query

Without having class definitions its a little difficult to give you specifics, but you could try to add something like:
var data = await _context.Set<OwnerProfile>()
.Include(x => x.OwnerBalances)
.Where(x => x.ThirdPartyRefId== ownerProfileId)
.Select(x => new
{
x.Prop1,
x.Prop2,
CustomerBalance = x.CustomerBalance.Value != null ? x.CustomerBalance.Value : 0
})
.ToListAsync();
You may be able to move the select statement outside the query too:
var newData = data.Select(x => new
{
x.Prop1,
x.Prop2,
CustomerBalance = x.CustomerBalance.Value != null ? x.CustomerBalance.Value : 0
})
You might be able to use conditional access in the 2nd version, but possibly not in the first.

Related

Is it possible to combine small queries into single query?

I have the following queries:
var truckCount = await DbContext.Trucks
.Where(t => t.Departure == null)
.CountAsync();
var firstTruck = await DbContext.Trucks
.Where(t => t.Departure == null)
.MinAsync(t => t.Arrival);
var railcarCount = await DbContext.Railcars
.Where(r => r.Departure == null)
.CountAsync();
var firstRailcar = await DbContext.Railcars
.Where(t => t.Departure == null)
.MinAsync(t => t.Arrival);
Can anyone tell me if it's possible to combine these queries into one so that there is only one round trip to the database?
I'd be looking to generate a query something like this.
select
(select count(*) from Trucks where Departure is null) as TruckCount,
(select min(Arrival) from Trucks where Departure is null) as FirstTruck,
(select count(*) from Railcars where Departure is null) as RailcarCount,
(select min(Arrival) from Railcars where Departure is null) as FirstRailcar
My backend is SQL Server.
You need to use a third party library which enables to execute multiple queries in a single roundtrip to the database. Maybe this extension with it's future queries works for you.
Otherwise you could implement a stored-procedure which encapsulates the queries (as subqueries) and returns the desired information.
Another option might be to just use 2 queries instead of 4:
var truckInfo = await DbContext.Trucks
.GroupBy(t => t.Departure == null)
.Where(g => g.Key == true)
.Select(g => new { Count = g.Count(), FirstTruck = g.Min(t => t.Arrival) })
.FirstOrDefaultAsync() ?? new { Count = 0, FirstTruck = DateTime.MinValue };
// same for Railcars
Not with linq, no. Why? Because of two reasons:
Query syntax has no way to get count and use union to get from one query
Method count, is immediate execution and not deferred, so you can't chain into one query
To be honest, that would be difficult to achieve even with a sql query as the data has different data types and columns.
Just in case, there is EF Core extension linq2db.EntityFrameworkCore (note that I'm one of the creators) which can run this query and almost any SQL ANSI query via LINQ
using var db = DbContext.CreateLinqToDBConnection();
var trucks = DbContext.Trucks
.Where(t => t.Departure == null);
var railcars = DbContext.Railcars
.Where(r => r.Departure == null);
var result = await db.SelectAsync(() => new
{
TruckCount = trucks.Ccount(),
FirstTruck = trucks.Min(t => t.Arrival),
RailcarCount = railcars.Count(),
FirstRailcar = railcars.Min(t => t.Arrival)
});

Linq select into model and set properties

In .NET Core 2.X, I was able to use this code below:
var bookings = await db.Tasks
.Where(c => c.ClientId == clientId && c.IsDeleted == false && c.Start > startOfThisMonth && c.End < endOfThisMonth)
.OrderBy(x => x.Start)
.Select(x => new SpecialTaskVm(new TaskViewModel(x, null))
{
client = x.Client,
carer = x.Booking.SingleOrDefault(b => b.SlotNumber == 1).Carer,
carer2 = x.Booking.SingleOrDefault(bk => bk.SlotNumber == 2).Carer
})
.ToListAsync();
However the same code in .net core 3.X results in this error:
System.InvalidOperationException: When called from 'VisitMemberInit', rewriting a node of type 'System.Linq.Expressions.NewExpression' must return a non-null value of the same type. Alternatively, override 'VisitMemberInit' and change it to not visit children of this type.
I could really do with selecting in the way I do above as each model does some modification to some properties and each model is used elsewhere separately.
I am also trying to avoid a foreach as it seems that would be inefficient.
I have tried passing the properties I need to set, into the model and setting them in the model like that. Same error occurs.
//This action method will return data for current month.
var startOfThisMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
var endOfThisMonth = new DateTime(DateTime.Now.Year, DateTime.Now.AddMonths(1).Month, 1);
var bookings = await db.Tasks
.Where(c => c.ClientId == clientId && c.IsDeleted == false && c.Start > startOfThisMonth && c.End < endOfThisMonth)
.OrderBy(x => x.Start)
.Select(x => new SpecialTaskVm(new TaskViewModel(x, null))
{
client = x.Client,
carer = x.Booking.SingleOrDefault(b => b.SlotNumber == 1).Carer,
carer2 = x.Booking.SingleOrDefault(bk => bk.SlotNumber == 2).Carer
})
.ToListAsync();
I expect for the list of tasks to be returned in the form of List<SpecialTaskVm> with Client, Carer and Carer2 set.
It's a bit unusual to use a constructor and object initialisation syntax in the same code, to me that's already a code smell.
If I were you, I would create an intermediate list that only gets values from the database, then project that data into your SpecialTaskVm objects. For example:
// First get the data from the database in a simple form we can parse through later
var bookingData = await db.Tasks
.Where(c => c.ClientId == clientId && c.IsDeleted == false && c.Start > startOfThisMonth && c.End < endOfThisMonth)
.OrderBy(x => x.Start)
.Select(x => new // Use an anonymous type
{
Client = x.Client,
Carer = x.Booking.SingleOrDefault(b => b.SlotNumber == 1).Carer,
Carer2 = x.Booking.SingleOrDefault(bk => bk.SlotNumber == 2).Carer
})
.ToListAsync();
// Now we massage the data into a format we can use
var bookings = bookingData
.Select(x => new SpecialTaskVm(new TaskViewModel(x, null))
{
client = x.Client,
carer = x.Carer,
carer2 = x.Carer2
})
.ToList();
Additionally, I would potentially recommend changing the SpecialTaskVm constructor (or add a new one) to include the new fields.

"Value cannot be null. Parameter name: source" when running a nested query on entity framework

I have the following code where I get error when loading Peers:
Value cannot be null. Parameter name: source
I am using FirstOrDefault and DefaultIfEmpty methods, and inside the select statement I am also checking if the object is empty m => m == null ?. But, I cannot avoid the error. Any ideas?
ReviewRoundDTO_student results = _context.ReviewRounds
.Include(rr => rr.ReviewTasks).ThenInclude(rt => rt.ReviewTaskStatuses)
.Include(rr => rr.Submissions).ThenInclude(s => s.PeerGroup.PeerGroupMemberships).ThenInclude(m => m.User)
.Include(rr => rr.Rubric)
.Where(rr => rr.Id == reviewRoundId)
.Select(rr => new ReviewRoundDTO_student
{
Id = rr.Id,
SubmissionId = rr.Submissions.FirstOrDefault(s => s.StudentId == currentUser.Id).Id,
Peers = rr.Submissions.FirstOrDefault(s => s.StudentId == currentUser.Id)
.PeerGroup.PeerGroupMemberships.DefaultIfEmpty()
.Select(m => m == null ? new ApplicationUserDto { } : new ApplicationUserDto
{
//FullName = m.User.FullName,
//Id = new Guid(m.UserId)
}),
}).FirstOrDefault();
Try avoiding FirstOrDefault().Something construct - expression trees do not support ?. operator which you'd normally use in similar LINQ to Objects query, and EF Core currently has issues translating it correctly - if you look at the exception stack trace, most likely the exception is coming deeply from EF Core infrastructure with no user code involved.
I would recommend rewriting the LINQ query w/o such constructs, for instance something like this:
var results = _context.ReviewRounds
.Where(rr => rr.Id == reviewRoundId)
.Select(rr => new ReviewRoundDTO_student
{
Id = rr.Id,
SubmissionId = rr.Submissions
.Where(s => s.StudentId == currentUser.Id)
.Select(s => s.Id)
.FirstOrDefault(),
Peers = rr.Submissions
.Where(s => s.StudentId == currentUser.Id)
.Take(1)
.SelectMany(s => s.PeerGroup.PeerGroupMemberships)
.Select(m => new ApplicationUserDto
{
FullName = m.User.FullName,
Id = m.UserId
})
.ToList(),
})
.FirstOrDefault();
Note that Include / ThenInclude are not needed in projection queries, because they are ignored.

How can i get List along with child list where child list having some condition in NHibernate

I had written a Query in NHibernate as below:
var queryResult = CurrentSession.QueryOver()
.Where(r => r.StatusId == 1)
.JoinQueryOver(a => a.ActorList)
.Where(s=>s.IsActor==1)
.List()
.Distinct()
.ToList();
I am trying to retrieve only Where(s=>s.IsActor==1), But It Is Getting Records
Where(s=>s.IsActor==0) also...
How can I get only IsActor==1 records?
Thanks in Advance
You need to specify a predicate in the join, so that it is applied to the join not the top where:
(will look something like ...LEFT JOIN actor on actor.Id = p.ActorId AND IsActor = 1)
Actor actorAlias = null;
var queryResult = CurrentSession.QueryOver()
.Where(r => r.StatusId == 1)
.Left.JoinQueryOver(r => r.ActorList, () => actorAlias, a => a.IsActor==1)
.List()
.Distinct()
.ToList();

Flattening Complex LINQ to SQL

I have a somewhat complex LINQ to SQL query that I'm trying to optimise (no, not prematurely, things are slow), that goes a little bit like this;
IQueryable<SearchListItem> query = DbContext.EquipmentLives
.Where(...)
.Select(e => new SearchListItem {
EquipmentStatusId = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null).Id,
StatusStartDate = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null).DateFrom,
...
});
The where clauses aren't important, they don't filter EquipmentStatuses, happy to include if someone thinks they're required.
This is on quite a large set of tables and returns a fairly details object, there's more references to EquipmentStatuses, but I'm sure you get the idea. The problem is that there's quite obviously two sub-queries and I'm sure that (among some other things) is not ideal, especially since they are exactly the same sub-query each time.
Is it possible to flatten this out a bit? Perhaps it's easier to do a few smaller queries to the database and create the SearchListItem in a foreach loop?
Here's my take given your comments, and with some assumptions I've made
It may look scary, but give it a try, with and without the ToList() before the GroupBy()
If you have LinqPad, check the SQL produced, and the number of queries, or just plug in the SQL Server Profiler
With LinqPad you could even put a Stopwatch to measure things precisely
Enjoy ;)
var query = DbContext.EquipmentLives
.AsNoTracking() // Notice this!!!
.Where(...)
// WARNING: SelectMany is an INNER JOIN
// You won't get EquipmentLive records that don't have EquipmentStatuses
// But your original code would break if such a case existed
.SelectMany(e => e.EquipmentStatuses, (live, status) => new
{
EquipmentLiveId = live.Id, // We'll need this one for grouping
EquipmentStatusId = status.Id,
EquipmentStatusDateTo = status.DateTo,
StatusStartDate = status.DateFrom
//...
})
// WARNING: Again, you won't get EquipmentLive records for which none of their EquipmentStatuses have a DateTo == null
// But your original code would break if such a case existed
.Where(x => x.EquipmentStatusDateTo == null)
// Now You can do a ToList() before the following GroupBy(). It depends on a lot of factors...
// If you only expect one or two EquipmentStatus.DateTo == null per EquipmentLive, doing ToList() before GroupBy may give you a performance boost
// Why? GroupBy sometimes confuses the EF SQL generator and the SQL Optimizer
.GroupBy(x => x.EquipmentLiveId, x => new SearchListItem
{
EquipmentLiveId = x.EquipmentLiveId, // You may or may not need this?
EquipmentStatusId = x.EquipmentStatusId,
StatusStartDate = x.StatusStartDate,
//...
})
// Now you have one group of SearchListItem per EquipmentLive
// Each group has a list of EquipmenStatuses with DateTo == null
// Just select the first one (you could do g.OrderBy... as well)
.Select(g => g.FirstOrDefault())
// Materialize
.ToList();
You don't need to repeat the FirstOrDefault. You can add an intermediate Select to select it once and then reuse it:
IQueryable<SearchListItem> query = DbContext.EquipmentLives
.Where(...)
.Select(e => e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null))
.Select(s => new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
In query syntax (which I find more readable) it would look like this:
var query =
from e in DbContext.EquipmentLives
where ...
let s = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null)
select new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
There is another problem in your query though. If there is no matching EquipmentStatus in your EquipmentLive, FirstOrDefault will return null, which will cause an exception in the last select. So you might need an additional Where:
IQueryable<SearchListItem> query = DbContext.EquipmentLives
.Where(...)
.Select(e => e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null))
.Where(s => s != null)
.Select(s => new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
or
var query =
from e in DbContext.EquipmentLives
where ...
let s = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null)
where s != null
select new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
Given that you don't test for null after calling FirstOrDefault(s => s.DateTo == null) I assume that:
either for each device there is always a status with DateTo == null or
you need to see only devices which have such status
In order to do so you need to join EquipmentLives with EquipmentStatuses to avoid subqueries:
var query = DbContext.EquipmentLives
.Where(l => true)
.Join(DbContext.EquipmentStatuses.Where(s => s.DateTo == null),
eq => eq.Id,
status => status.EquipmentId,
(eq, status) => new SelectListItem
{
EquipmentStatusId = status.Id,
StatusStartDate = status.DateFrom
});
However, if you do want to perform a left join replace DbContext.EquipmentStatuses.Where(s => s.DateTo == null) with DbContext.EquipmentStatuses.Where(s => s.DateTo == null).DefaultIfEmpty().

Categories

Resources