Entity Framework loading some, none or all navigation properties dynamically - c#

Looking at this documentation I can see that you can load multiple navigation entities using the following syntax:
using (var context = new DbContext())
{
var userDocs = context.UserDocuments
.Include(userDoc => userDoc.Role.User)
.ToList();
}
This will give me Role and User navigation properties hung off my UserDocument object, however if I want to use the string overload of Include, how might I construct the code to handle multiple includes?
This does not work:
return await ctx.UserDocuments.Where(x => x.UserId == userId)
.Include("Role.User").ToList();
I am trying to do it this way as my methods may want some, all or no navigation properties returned depending on the calling code. My intention is to add a string array to the repository method which will build any required navigation properties accordingly. If this is the wrong approach, does anyone have another recommendation, I'm wondering if lazy loading would be a more elegant solution...?
Edit
This is the Entity which has nav props:
public partial class UserDocument
{
public int Id { get; set; }
public Guid UserId { get; set; }
public int RoleId { get; set; }
public int AccountId { get; set; }
public virtual Role Role { get; set; } = null!;
public virtual User User { get; set; } = null!;
}

I think you are looking for something like this:
public async Task<List<UserDocument>> MyMethod(List<string> propertiesToInclude)
{
IQueryable<UserDocument> currentQuery = _context.UserDocuments.Where(x => x.UserId == userId);
foreach(var property in propertiesToInclude)
{
currentQuery = currentQuery.Include(property);
}
return await currentQuery.ToListAsync();
}

If you're using the Include(String) method, you don't need to include the lambda to specify the property path.
Instead of doing .Include(x => "Role.User"), try .Include("Role.User")

First of all, you must told us what is "Role.User"?
We cannot answer you if we don't know excactly what you wrote.
So, now we are know that are two differents entities you can do this one
var userDocs = await context.UserDocuments
.Include(x => x.Role)
.ThenInclude(x => x.User)
.ToListAsync();
I hope this one helps you. :)

Related

Filter a property List inside in include

I need your help
I try to create a linq sentence with .Include but my problem is that i have a property in mi class witch is a list, it is my class specifically:
public partial class document
{
public int ID { get; set; }
public string Amount { get; set; }
public List<Log> Log { get; set; }
}
this is the class log
public partial class Log
{
[Key]
public int ID { get; set; }
[Required]
public Status Status { get; set; }
[Column(TypeName = "text")]
public string Description { get; set; }
public DateTime? DateLog { get; set; }
public int? DocumentID{ get; set; }
[ForeignKey("DocumentID")]
public Document Document{ get; set; }
}
my problem is that I don't know how to filter my list record inside the document for include in the class, I need to get the whole document class and filter the log that only shows status = recieved, a document can have many logs
y tried to do that but it didnĀ“t work
var Result = db.document
.Include(m => m.Log.Where(c => c.Status == Status.Recieved));
i recived the next error
"the include path expression must refer to a navigation property defined on the type. use dotted paths for reference navigation properties and the select operator for collection navigation properties.\r\nparameter name: path"
I appreciate your help
Include used for include relationships with an entity and fetch related entity properties, check documentation - Fetching related data
If you select documents without Include like this
var documents = await db.document.ToListAsync();
you get documents data where Log will be null.
You need something like that:
var result = await db.document
.Select(w=> new
{
document = w,
log = w.Log.Where(c => c.Status == Status.Recieved).ToList()
}).ToListAsync();
EF does support some automatic filtering rules to help with concepts like soft-delete (IsActive) and multi-tenancy (ClientId), but not really applicable for scenarios like this where you want to apply a situational filter like "received" documents.
EF entities should be considered as models reflecting the data state. To filter results like that is more of a view model state which you can achieve through projection:
var result = db.document.Select(d => new DocumentViewModel
{
DocumentId = d.DocumentId,
// .. fill in other required details...
ReceivedLogs = d.Logs
.Where(l => l.Status == Status.Received)
.Select(l => new LogViewModel
{
// Fill needed log details...
}).ToList()
}).ToList();
Otherwise if you are doing something local with the entities and just want the document and the received log entries:
var documentDetails = db.document
.Where(d => d.DocumentId == documentId)
.Select(d => new
{
Document = d,
ReceivedLogs = d.Logs
.Where(l => l.Status == Status.Received)
.ToList()
}).Single();
documentDetails.Document.Logs will not be eager loaded, and would trigger lazy loading if you access it, but the documentDetails does contain the relevant Received logs to access. As an anonymous type it's not suitable to being returned, only consumed locally.

Using Include vs ThenInclude

I have been experimenting a little with Entity Framework, and after facing the error below, I tried using ThenInclude to resolve it.
The expression '[x].ModelA.ModelB' passed to the Include operator could not be bound
But now it seems I lack some understanding of why it did solve the problem
What's the difference between this:
.Include(x => x.ModelA.ModelB)
And this:
.Include(x => x.ModelA).ThenInclude(x => x.ModelB)
"Include" works well with list of object, but if you need to get multi-level data, then "ThenInclude" is the best fit. Let me explain it with an example. Say we have two entities, Company and Client:
public class Company
{
public string Name { get; set; }
public string Location { get; set; }
public List<Client> Clients {get;set;}
}
public class Client
{
public string Name { get; set; }
public string Domains { get; set; }
public List<string> CountriesOfOperation { get; set; }
}
Now if you want just companies and the entire client list of that company, you can just use "Include":
using (var context = new YourContext())
{
var customers = context.Companies
.Include(c => c.Clients)
.ToList();
}
But if you want a Company with "CountriesOfOperation" as related data, you can use "ThenInclude" after including Clients like below:
using (var context = new MyContext())
{
var customers = context.Companies
.Include(i => i.Clients)
.ThenInclude(a => a.CountriesOfOperation)
.ToList();
}
The difference is that Include will reference the table you are originally querying on regardless of where it is placed in the chain, while ThenInclude will reference the last table included. This means that you would not be able to include anything from your second table if you only used Include.

Can't get an entity property

There is a problem with my Db which I figured only now, when I started to work at the web api. My USER entity:
public class User { get; set; }
{
public int UserId { get; set; }
public string Name { get; set; }
}
And this is ACTIVITY
public class Activity
{
public int ActivityId { get; set; }
public User User { get; set; }
}
I added an activity and checked in SSMS. Everything seems to be good, there is a field named UserId which stores the id. My problem is when I try to get a User from an Activity because I keep getting null objects. I didn't set anything special in my DbContext for this.
This is where I'm trying to get an User from an Activity object:
public ActionResult ActivityAuthor(int activityId)
{
Activity activityItem = unitOfWork.Activity.Get(activityId);
return Json(unitOfWork.User.Get(activityItem.User.UserId));
}
Relation between User and Activity
The User property of Activity class should be marked as virtual. It enables entity framework to make a proxy around the property and loads it more efficiently.
Somewhere in your code you should have a similar loading method as following :
using (var context = new MyDbContext())
{
var activity = context.Activities
.Where(a => a.ActivityId == id)
.FirstOrDefault<Activity>();
context.Entry(activity).Reference(a => a.User).Load(); // loads User
}
This should load the User object and you won't have it null in your code.
Check this link for more information msdn
my psychic debugging powers are telling me that you're querying the Activity table without Include-ing the User
using System.Data.Entity;
...
var activities = context.Activities
.Include(x => x.User)
.ToList();
Alternatively, you don't need Include if you select properties of User as part of your query
var vms = context.Activities
.Select(x => new ActivityVM() {UserName = x.User.Name})
.ToList();

Controller returns a list of data instead of single item, how to solve this issue?

Background
Recently I changed jobs and attached to a Web API project. I am familiar with the concepts of Web API & MVC but have no prior hands-on experince.
I have followed few tutorials and based on them created an empty WebApi project via Visual Studio 2017, hooked up my model from Database and added Controllers.
This is the revised controller:
private MyEntities db = new MyEntities();
//...
[ResponseType(typeof(MyEntityType))]
[Route("api/MyEntity")]
public async Task<IHttpActionResult> GetMyEntityType([FromUri]int parameter)
{
MyEntityType found = db.MyEntity
.OrderByDescending(c => c.CreationTime)
.First(c => c.ParameterColumn == parameter);
if (found == null)
{
return NotFound();
}
return Ok(found );
}
Note : I am querying based on a column other than KEY
When I make a call to .../api/MyEntity?parameter=1 I expect to receive a single item in response. But for reasons unknown to me, the previous call returns all items and it is unsorted.
Please note: If I place a breakpoint on if (found == null), I can confirm that my query has resulted in a single item.
Question
What am I missing here? Why does the response contains all elements instead of single element?
UPDATE 1
I tried the same call from Postman, this is the output. Please note that I have changed the request, controller code etc. in question to omit some private details.
I can see that response contains my desired data, but along with all of inner data in other end of relationship. If I am not mistaken, by default, EF uses lazy loading. Since I have no Include clause, I have no idea why all related data is returned in response.
I think I need to investigate my relationships in Model/DB and make sure Lazy-Loading is enabled.
UPDATE 2
These are my entity classes:
public partial class MyEntity
{
public int Id { get; set; }
public Nullable<int> ForeignKey_ID { get; set; }
public Nullable<int> MyValue { get; set; }
public Nullable<System.DateTime> CreationTime { get; set; }
public Nullable<int> Some_ID { get; set; }
public virtual MyOtherEntity MyOtherEntity { get; set; }
}
public partial class MyOtherEntity
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public MyOtherEntity()
{
this.MyOtherEntity1 = new HashSet<MyOtherEntity>();
}
//...
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MyOtherEntity> MyOtherEntity1 { get; set; }
public virtual MyOtherEntity MyOtherEntity2 { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MyEntity> MyEntity { get; set; }
}
When the MyEntityType instance is returned via Ok it will be converted to JSON which will read the values of all the public properties and fields. This will cause EF to load the entire entity and all relationships. If you only require specific properties to be returned then use Select() as below.
var found = db.MyEntity
.OrderByDescending(c => c.CreationTime)
.Select(c => new { c.CreationTime, c.ParameterColumn })
.First(c => c.ParameterColumn == parameter);
You can tailor the selected properties as required. If you require criteria in First() that does not need to be selected, move the condition into a Where() call before the Select.
var found = db.MyEntity
.OrderByDescending(c => c.CreationTime)
.Where(c => c.ParameterColumn == parameter)
.Select(c => new { c.CreationTime })
.First();
You should probably create a ViewModel of MyEntityType that has only the properties you require, and map these to a new instance of the ViewModel instead. You can then update the [ResponseType(typeof(MyEntityType))] attribute too.
For example, declare:
public class MyEntityTypeViewModel {
public DateTime CreationTime { get; set; }
public int ParameterColumn { get; set; }
}
And then in your controller action:
MyEntityTypeViewModel found = db.MyEntity
.OrderByDescending(c => c.CreationTime)
.Where(c => c.ParameterColumn == parameter)
.Select(c => new MyEntityTypeViewModel {
CreationTime = c.CreationTime,
ParameterColumn = c.ParameterColumn })
.First();
If you're using Entity Framework Core 2, try this:
MyEntityType found = await db.MyEntity.AsNoTracking()
.OrderByDescending(c => c.CreationTime)
.FirstOrDefaultAsync(c => c.ParameterColumn == parameter);
And it's better to include database context via Dependency Injection, not with private field.

Filter linq/entity query results by related data

I'm using MVC5 EF6 and Identity 2.1.
I have two classes:
public class Incident
{
public int IncidentId {get; set;}
...//Title, Description, etc
public virtual ICollection<FollowedIncident> FollowedIncidents { get; set; }
public virtual ApplicationUser User { get; set; }
}
public class FollowedIncident
{
public int FollowedIncidentId { get; set; }
public string UserId { get; set; }
public int IncidentId { get; set; }
public virtual Incident Incident { get; set; }
public virtual ApplicationUser User { get; set; }
}
So, the users will have the ability to follow an incident. (For starters, I'm not entirely sure if I need the ICollection and public virtual relationship references, but added them just in case for the time being.)
I'm trying to create the query that will show users the results of their followed incidents. In my controller, my query starts like this (I'm using Troy Goode's paging package... i.e. listUnpaged):
IQueryable<Incident> listUnpaged = db.Incidents.OrderByDescending(d => d.IncidentDate);
Then I want to filter by followed incidents. So, I want to show incidents where userId (parameter I pass to it) is equal to UserId in FollowedIncident. I've tried like this (error about conversion to bool from IEnumerable):
listUnpaged = listUnpaged.Where(s => s.FollowedIncidents.Where(t => t.UserId == userId));
And this (no error, but doesn't filter at all):
listUnpaged = listUnpaged.Where(s => s.FollowedIncidents.All(t => t.UserId == userId));
To me, it seems it should be as simple as this:
listUnpaged = listUnpaged.Where(s => s.FollowedIncidents.UserId == userId));
But, the linq extensions don't seem to like related data child properties? (I apologize for my programming terminology as I haven't quite pieced together all the names for everything yet.)
Anyone know how to accomplish this? It seems I may not even be thinking about it correct? (...since in the past, I've always used related data to supplement or add properties to a result. This will be the first time I want to narrow results by related data.)
Thank you.
Actually you're going about getting the Incidents the wrong way.. since Incident is a navigation property of FollowedIncident you should just use
IQueryable<Incident> listUnpaged = db.FollowedIncidents
.Where(a => a.UserId == userid)
.Select(a => a.Incident)
.OrderByDescending(d => d.IncidentDate);
Another option is to use Any()
IQueryable<Incident> listUnpaged = db.Incidents
.Where(a => a.FollowedIncidents.Any(b => b.UserId == userid)
.OrderByDescending(d => d.IncidentDate);
which would be like saying
Select *
From Incidents
Where Id IN (Select IncidentId
From FollowedIncident
Where UserId = #UserId)

Categories