Include a collection and then a collection one level down - c#

I am trying to query a collection and child collection using EF 7. Here's the code:
public async Task < List < Table >> GetAllTable() {
var tableList = await db.Tables.Include(o => o.Checks.Select(i => i.CheckItems)).ToListAsync();
return tableList;
}
I'm following the syntax from here MSDN. However, when i run this code i'm getting the following error. Does anyone know what went wrong here? Thanks!
InvalidCastException: Unable to cast object of type
'Remotion.Linq.Clauses.Expressions.SubQueryExpression' to type
'System.Linq.Expressions.MemberExpression'.**
Microsoft.Data.Entity.Query.EntityQueryModelVisitor.b__30_2(<>f__AnonymousType2`2
<>h__TransparentIdentifier0)

The documentation you are reading is for EF 5.
The design meeting notes for EF 7 say the syntax of this has changed - try this:
db.Tables.Include(t => t.Checks)
.ThenInclude(c => c.CheckItems)
.ToListAsync()

Related

.Net 7 Minimal API - using .Where causing error

I am doing some learning on .Net 7 and minimal API's
I am simply trying to return a list of all timesheets within a payperiod. but I get the following error
Error CS1061 'Task<List>' does not contain a definition for
'Where' and no accessible extension method 'Where' accepting a first
argument of type 'Task<List>' could be found (are you
missing a using directive or an assembly reference?)
group.MapGet("/Payperiod/{payperiod}", async Task<Results<Ok<Timesheet>, NotFound>> (string payperiod, TimesheetAPIContext db) =>
{
return await db.Timesheet.ToListAsync().Where(t => t.Contains(payperiod))
is Timesheet model
? TypedResults.Ok(model)
: TypedResults.NotFound();
})
.WithName("GetTimesheetByPayPeriod")
.WithOpenApi();
Can someone help me understand what is going on here and how I should be writing this endpoint?
Cheers
First, you should put the where() before ToListAsync(), simply, you can think all linq's meothd end with Async is final method, you should not chain invoke after it.
Second, you can not use the Is Timesheet to check where it is empty. you should use the Any().
the modifed code as below:
group.MapGet("/Payperiod/{payperiod}", async Task<Results<Ok<Timesheet>, NotFound>> (string payperiod, TimesheetAPIContext db) =>
{
var ret = await db.Timesheet.Where(t => t.Contains(payperiod)).ToListAsync();
return ret.Any()
? TypedResults.Ok(ret)
: TypedResults.NotFound();
})
.WithName("GetTimesheetByPayPeriod")
.WithOpenApi();
You can't use .Where on a Task you need to first call .Result to actually get a List.
return await db.Timesheet.ToListAsync().Result.Where(t => t.Contains(payperiod))
is Timesheet model
? TypedResults.Ok(model)
: TypedResults.NotFound();
First of all, it looks like (looking at your responses in comments) you are writing asynchronous code without knowing how it works. Please correct me if I got that wrong.
The thing is, asynchronous programming can be very complex if you have no idea of what you are doing.
Have a look at these sources to understand asynchronous programming:
https://auth0.com/blog/introduction-to-async-programming-in-csharp/
https://www.codingame.com/playgrounds/4240/your-ultimate-async-await-tutorial-in-c/introduction
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model
Now let's take a look at your code:
group.MapGet("/Payperiod/{payperiod}", async Task<Results<Ok<Timesheet>, NotFound>> (string payperiod, TimesheetAPIContext db) =>
{
return await db.Timesheet.ToListAsync().Where(t => t.Contains(payperiod))
is Timesheet model
? TypedResults.Ok(model)
: TypedResults.NotFound();
})
.WithName("GetTimesheetByPayPeriod")
.WithOpenApi();
This line return await db.Timesheet.ToListAsync().Where(t => t.Contains(payperiod)) is where the problem exists.
You are trying to retrieve the Timesheet table from the database, then asynchronously convert it to a List<> and filter the result.
The problem is that you should wait for the ToListAsync() task to finish before you can filter the result with Where.
This is one example of how you can fix it (mind the extra parentheses):
group.MapGet("/Payperiod/{payperiod}", async Task<Results<Ok<Timesheet>, NotFound>> (string payperiod, TimesheetAPIContext db) =>
{
return (await db.Timesheet.ToListAsync()).Where(t => t.Contains(payperiod))
is Timesheet model
? TypedResults.Ok(model)
: TypedResults.NotFound();
})
.WithName("GetTimesheetByPayPeriod")
.WithOpenApi();

Using EF Include on TPH

I have implemented code first db architecture with simple inheritance using THP:
And I need to query all notifications of all type.
TargetUser property in NotificationUser table is a association.
I'm trying to execute next code:
var notifications = _context.Notifications;
foreach (var notification in notifications)
{
Debug.WriteLine((notification is NotificationUser)? ((NotificationUser) notification).TargetUser?.Name : "-");
}
In database propety TargetUser is set to correct foreign key, but in code I don't get any result. Lazy loading is enabled.
Is it possible to user eager loading? I had already tried to write _context.Notifications.Include('TargetUser') byt it throws an exception.
Upd. The exception is:
A specified Include path is not valid. The EntityType 'Core.Concrete.NotificationBase' does not declare a navigation property with the name 'TargetUser'.
Tried to modify this answer to:
var notifications = _context.Notifications.OfType<NotificationUser>()
.Include(n => n.TargetUser)
.Cast<NotificationBase>()
.Union(_context.Notifications.OfType<NotificationPlace>()
but still the same exception is thrown.
I know this is an old thread, but I'd still like to post some improvements for somebody looking for the same solution.
1. Network Redundancy
Selecting Ids and then running a query, that loads items with the Ids is redundant and the same effect can be achieved by simply running this
Solution:
var userNotifications = _context.Notifications
.OrderByDescending(n => n.DateTime)
.Skip(offset)
.Take(limit)
.OfType<NotificationUser>()
.Include(n => n.TargetUser)
.Include(n => n.TargetUser.Images)
.ToList();
That way, you aren't waiting for 2 DB connections, but just one. Also you save some traffic.
2. Paging on ignored entities?
One would assume, that this specific method is used for only viewing Entities of an inherited type so I would expect Skip and Take to work directly on only entities of said type. e.g. I want to skip 10 NotificationUsers, not 10 Users (of which only 4 are NotificationUsers for example).
Solution: Move ofType higher up the query
var userNotifications = _context.Notifications
.OfType<NotificationUser>()
.OrderByDescending(n => n.DateTime)
.Skip(offset)
.Take(limit)
.Include(n => n.TargetUser)
.Include(n => n.TargetUser.Images)
.ToList();
3. Async/Await
When writing an API, you should think about using async/await as that doesn't block the thread thus wastes less resources (this will probably require you to rewrite a lot of your existing code if you don't use it already though).
Please study the advantages of async/await and use them in scenarios like waiting for a result.
Solution: Change this
private List<NotificationUser> GetNotificationUsers(int offset, int limit)
{
return _context.Notifications
.OfType<NotificationUser>()
.OrderByDescending(n => n.DateTime)
.Skip(offset)
.Take(limit)
.Include(n => n.TargetUser)
.Include(n => n.TargetUser.Images)
.ToList();
}
into this
private async Task<List<NotificationUser>> GetNotificationUsersAsync(int offset, int limit)
{
return await _context.Notifications
.OfType<NotificationUser>()
.OrderByDescending(n => n.DateTime)
.Skip(offset)
.Take(limit)
.Include(n => n.TargetUser)
.Include(n => n.TargetUser.Images)
.ToListAsync();
}
NOTE:
You also then have to change any place that uses this method from
var x = GetNotificationUsers(skip, take);
to
var x = await GetNotificationUsersAsync(skip, take);
And make that method async and return a task as well
I don't know about the amount of entities you will work with. If possible, I would try to do the union not on the DB server:
var userNotifications = _context.Notifications.OfType<NotificationUser>()
.Include(n => n.TargetUser).ToList();
var placeNotifications = _context.Notifications.OfType<NotificationPlace>().ToList();
var notifications = userNotifications.Union(placeNotifications);
See https://stackoverflow.com/a/27643393/2342504
Already tried a lot of different solutions, and none fit my requirements, as I'm working on an API, and the query must support pagination and make constant requests to the database (and not loading all entities in memory).
Finally found out a solution, perhaps not the best, but enough for now.
Firstly, I request a portion of ordered data (pagination logic):
var notifications = _context.Notifications
.OrderByDescending(n => n.DateTime)
.Skip(offset)
.Take(limit);
(At this point I'm not interested in any properties) Next, I'm getting the Id's of loaded items for each entity type:
var ids = notifications.OfType<NotificationUser>().Select(n => n.Id).ToList();
and lastly load specific entities including all properties:
var userNotifications = _context.Notifications.OfType<NotificationUser>()
.Include(n => n.TargetUser)
.Include(n => n.TargetUser.Images)
.Where(n => ids.Contains(n.Id))
.ToList();
all entities goes to list and sorted one more time.
A lot of bad stuff here, hope someone can provide better solution.
If I understood well, you want to get all the entities from the table + the property TargetUser when relevant (for entities of type NotificationUser). You said that "Lazy loading is enabled", which is a good thing.
You tried something like (updated to latest version of C#):
var notifications = _context.Notifications;
foreach (var notification in notifications)
{
Debug.WriteLine((notification is NotificationUser notificationUser)
? notificationUser.TargetUser?.Name
: "-");
}
If you don't get any result, it probably means that your entities are badly configured to work with lazy loading:
Are your classes public?
Are your navigation properties defined as public, virtual?
See: https://learn.microsoft.com/en-us/ef/ef6/querying/related-data#lazy-loading

Comparing with where clause using Dictionary<int,int> [duplicate]

This piece of code keep making this error. :
Unable to create a constant value of type 'Repository.DBModel.Subscriber'. Only primitive types or enumeration types are supported in this context.
I've Changed it a few times but it keeps coming up with this Error.
using (SubscriberDBHandler db = new SubscriberDBHandler())
{
IEnumerable <Subscriber> NewSubscribers = Subscribers
.Where(sub => db.Subscriber
.Any(aSub => !aSub.Email.Equals(sub.Email)));
List<Subscriber> updateSubscribers = db.Subscriber
.Where(dbSub => Subscribers
.Any(lSub => lSub.Email
.Equals(dbSub.Email))).ToList();
if(NewSubscribers.Count() >= 1)
{
db.Subscriber.AddRange(NewSubscribers);
}
updateSubscribers.ForEach(aSub => aSub.State = Subscribers
.FirstOrDefault(sub => sub.Email
.Equals(aSub.Email)).State ?? "Error" );
db.SaveChanges();
}
I'd greatly appreciate if someone could point out my error or come up with a more efficient way to do this.
In advance thanks for your time and help.
I know there are a few post with this error out there but when reading them I can't figure out how they relate to my problem. so I'm sorry if this is a common mistake and others have provided a solution
The object Subscribers is a List<Subscriber>
I don't seem to be able to find the line but. the stack trace does contain this.
at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)
at Repository.SubScribRepository.AddOrUpdateSubscribers(List1 Subscribers)
You use a local collection, Subscribers, directly in a LINQ statement. But these objects can't be translated into SQL. There are only mappings from primitive types to database types.
I'd suggest you use
var emails = Subscribers.Select(s => s.Email).ToList();
And proceed by using these strings (i.e. primitive values) in Contains statements like:
var newSubscribers = db.Subscriber
.Where(dbSub => !emails.Contains(dbSub.Email))
.ToList();
var updateSubscribers = db.Subscriber
.Where(dbSub => emails.Contains(dbSub.Email))
.ToList();
Changing updateSubscribers to an IEnumerable will prevent this error

Error: Only primitive types or enumeration types are supported in this context EF

This piece of code keep making this error. :
Unable to create a constant value of type 'Repository.DBModel.Subscriber'. Only primitive types or enumeration types are supported in this context.
I've Changed it a few times but it keeps coming up with this Error.
using (SubscriberDBHandler db = new SubscriberDBHandler())
{
IEnumerable <Subscriber> NewSubscribers = Subscribers
.Where(sub => db.Subscriber
.Any(aSub => !aSub.Email.Equals(sub.Email)));
List<Subscriber> updateSubscribers = db.Subscriber
.Where(dbSub => Subscribers
.Any(lSub => lSub.Email
.Equals(dbSub.Email))).ToList();
if(NewSubscribers.Count() >= 1)
{
db.Subscriber.AddRange(NewSubscribers);
}
updateSubscribers.ForEach(aSub => aSub.State = Subscribers
.FirstOrDefault(sub => sub.Email
.Equals(aSub.Email)).State ?? "Error" );
db.SaveChanges();
}
I'd greatly appreciate if someone could point out my error or come up with a more efficient way to do this.
In advance thanks for your time and help.
I know there are a few post with this error out there but when reading them I can't figure out how they relate to my problem. so I'm sorry if this is a common mistake and others have provided a solution
The object Subscribers is a List<Subscriber>
I don't seem to be able to find the line but. the stack trace does contain this.
at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)
at Repository.SubScribRepository.AddOrUpdateSubscribers(List1 Subscribers)
You use a local collection, Subscribers, directly in a LINQ statement. But these objects can't be translated into SQL. There are only mappings from primitive types to database types.
I'd suggest you use
var emails = Subscribers.Select(s => s.Email).ToList();
And proceed by using these strings (i.e. primitive values) in Contains statements like:
var newSubscribers = db.Subscriber
.Where(dbSub => !emails.Contains(dbSub.Email))
.ToList();
var updateSubscribers = db.Subscriber
.Where(dbSub => emails.Contains(dbSub.Email))
.ToList();
Changing updateSubscribers to an IEnumerable will prevent this error

Get entities that are not linked

I have Items and Lines that are linked with the ItemsLines table.
On a web page, I show an item. I want to display in a DropDownList all Lines that are not linked to this Item.
This doesn't work :
int ItemId = Convert.ToInt32(Request.QueryString["id"]);
ddlLines.DataSource = context.Lines.Where(t => !t.ItemLines.Any(x => x.ItemId == ItemId));
I was trying to get the Lines where(they are not associated to the Item).
I can't figure out how to do this.
Thank you very much!
EDIT :
This is the error message I get:
the objectcontext instance has been disposed and can no longer be used
Your error is not because your lambda expression is incorrect. Your issue is that the context object you are using to connect to your database has already been disposed of before you call:
ddlLines.DataSource = context.Lines.Where(t => !t.ItemLines.Any(x => x.ItemId == ItemId));
The solution is to add a .ToList() to the end of your expression. This is because the .Where lambda expression returns something from the IQueryable interface and everything from that interface still has a connection to the database. So by adding .ToList() it will remove the connection to the database.
First, the error message is because the context has been disposed (I assume you are using it inside a 'using' block, which means the context has been disposed by the time the actual data binding occurs). If you intend to directly bind to the items, you may want to use projection, which will create a new instance that is not connected to the DataContext:
ddlLines.DataSource = context.Lines.Where(
t => !t.ItemLines.Any(x => x.ItemId == ItemId)
).Select(
t => new
{
SomeProperty = t.SomeProperty,
SomeOtherProperty = t.SomeOtherProperty }).ToList();
// Where SomeProperty and SomeOtherProperty are the display/value fields, etc.
Second, you might want to consider using Except for this instead of your lambda. It'll return an IEnumerable of all things NOT in the set you supply, and it's a bit cleaner:
ddlLines.DataSource = context.Lines.Except(new int[] { ItemId }).Select(...).ToList();

Categories

Resources