Linq select into model and set properties - c#

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.

Related

cast to decimal in linq query

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.

Linq .net core 3.1 When called from 'VisitLambda', rewriting a node of type 'System.Linq.Expressions.ParameterExpression' must return

I have a problem with below exception:
When called from 'VisitLambda', rewriting a node of type
'System.Linq.Expressions.ParameterExpression' must return a non-null
value of the same type. Alternatively, override 'VisitLambda' and
change it to not visit children of this type.
it is related to this part of the code:
var teamEntries = _dataContext.TeamEntriesEntries
.Where(c => TeamEntryIds.Contains(c.Id))
.Include(c => c.TeamType);
.Select(c => new
{
UserId = filteredCachedEntries.Any() ? filteredCachedEntries.Where (f => f.CurrentCalendarEntry != null && f.CurrentCalendarEntry.Id == c.Id).FirstOrDefault().UserId : 0,
c.TeamType,
Status = c.Progress != null ? c.Progress.Status : null,
})
.ToList();
Below line cause error (Where):
UserId = filteredCachedEntries.Any() ? filteredCachedEntries.Where (f => f.CurrentCalendarEntry != null && f.CurrentCalendarEntry.Id == c.Id).FirstOrDefault().UserId : 0
in .net core 2.2 everything was ok but right now it doesn't work and show me exceptaion.
Extract the data you need from the server and then do client side processing on the client side data:
var teamEntries = _dataContext.TeamEntriesEntries
.Where(c => TeamEntryIds.Contains(c.Id))
.Select(c => new {
cID = c.Id,
c.TeamType,
Status = c.Progress.Status,
})
.AsEnumerable() // switch to client side processing
.Select(c => new {
UserId = filteredCachedEntries
.Where(f => f.CurrentCalendarEntry.Id == c.cID)
.Select(f => f.UserId)
.FirstOrDefault(),
c.TeamType,
c.Status
})
.ToList();
Well, query is wrong. EF Core 2.2 just evaluates everything on the client in that case. Removed everything that is not needed for correct SQL translation.
var teamEntries = _dataContext.TeamEntriesEntries
.Where(c => TeamEntryIds.Contains(c.Id))
.Select(c => new
{
UserId = filteredCachedEntries
.Where(f => f.CurrentCalendarEntry.Id == c.Id)
.Select(f => f.UserId)
.FirstOrDefault(),
c.TeamType,
Status = c.Progress.Status,
})
.ToList();
Variant via LEFT JOIN
var teamEntries =
from c in _dataContext.TeamEntriesEntries
where TeamEntryIds.Contains(c.Id)
from f in filteredCachedEntries
.Where(f => f.CurrentCalendarEntry.Id == c.Id)
.Take(1)
.DefaultIfEmpty()
select new
{
UserId = f.UserId,
c.TeamType,
Status = c.Progress.Status,
})
.ToList();

"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.

Using two Linq query in a single method

As shown in the below code, the API will hit the database two times to perform two Linq Query. Can't I perform the action which I shown below by hitting the database only once?
var IsMailIdAlreadyExist = _Context.UserProfile.Any(e => e.Email == myModelUserProfile.Email);
var IsUserNameAlreadyExist = _Context.UserProfile.Any(x => x.Username == myModelUserProfile.Username);
In order to make one request to database you could first filter for only relevant values and then check again for specific values in the query result:
var selection = _Context.UserProfile
.Where(e => e.Email == myModelUserProfile.Email || e.Username == myModelUserProfile.Username)
.ToList();
var IsMailIdAlreadyExist = selection.Any(x => x.Email == myModelUserProfile.Email);
var IsUserNameAlreadyExist = selection.Any(x => x.Username == myModelUserProfile.Username);
The .ToList() call here will execute the query on database once and return relevant values
Start with
var matches = _Context
.UserProfile
.Where(e => e.Email == myModelUserProfile.Email)
.Select(e => false)
.Take(1)
.Concat(
_Context
.UserProfile
.Where(x => x.Username == myModelUserProfile.Username)
.Select(e => true)
.Take(1)
).ToList();
This gets enough information to distinguish between the four possibilities (no match, email match, username match, both match) with a single query that doesn't return more than two rows at most, and doesn't retrieve unused information. Hence about as small as such a query can be.
With this done:
bool isMailIdAlreadyExist = matches.Any(m => !m);
bool isUserNameAlreadyExist = matches.LastOrDefault();
It's possible with a little hack, which is grouping by a constant:
var presenceData = _Context.UserProfile.GroupBy(x => 0)
.Select(g => new
{
IsMailIdAlreadyExist = g.Any(x => x.Email == myModelUserProfile.Email),
IsUserNameAlreadyExist = g.Any(x => x.Username == myModelUserProfile.Username),
}).First();
The grouping gives you access to 1 group containing all UserProfiles that you can access as often as you want in one query.
Not that I would recommend it just like that. The code is not self-explanatory and to me it seems a premature optimization.
You can do it all in one line, using ValueTuple and LINQ's .Aggregate() method:
(IsMailIdAlreadyExist, IsUserNameAlreadyExist) = _context.UserProfile.Aggregate((Email:false, Username:false), (n, o) => (n.Email || (o.Email == myModelUserProfile.Email ? true : false), n.Username || (o.Username == myModelUserProfile.Username ? true : false)));

How to select a list of distinct values based on some values using linq or entity

I want to get all the Pre_Number where all Reconcile_Status related to that Pre_Number=null. In this case there should not be any item in list.If there would be some other Pre_number for eg. 7/2018 and it has two records and Reconcile_Status for those records is NULL then i should get one item in list that is 7/2018.
I tried
var NoNReconciled = context.tbl_prerelease_invoice
.Where(x => x.Reconcile_Status==null)
.Select(y => new { y.Pre_number }).Distinct().ToList();
But i got 6/2018
Well, your current attempt only checks that there is at least one record where Reconcile_Status is null, but it doesn't check that there are no records with the same Pre_number where Reconcile_Status is not null.
This should do the trick:
var NoNReconciled = context.tbl_prerelease_invoice
.Where(x => x.Reconcile_Status == null &&
!context.tbl_prerelease_invoice
.Any(y => y.Pre_number == x.Pre_number && y.Reconcile_Status != null)
).Select(y => new { y.Pre_number })
.Distinct().ToList();
No need to create anonymous object for Pre_Number. Try below code
var NoNReconciled = context.tbl_prerelease_invoice
.Where(x => x.Reconcile_Status==null)
.Select(y => y.Pre_number).Distinct().ToList();
Try this-
context.tbl_prerelease_invoice.GroupBy(r => r.Pre_number).Where(kv => kv.All(r => r.Reconcile_Status==null)).Select(kv => kv.Key).ToList();

Categories

Resources