ASP.NET EF6 Query Model - c#

I have the following model. Each Module has a nested collection of children of type module. Each module also has a collection of Permissions.
[DataContract(IsReference = true)]
public class Module
{
[Key]
[DataMember]
public Guid ModuleId { get; set; }
[Required]
[StringLength(100)]
[DataMember]
public string Title { get; set; }
[StringLength(100)]
[DataMember]
public string Description { get; set; }
[StringLength(50)]
[DataMember]
public string Icon { get; set; }
[Required]
[RegularExpression(#"[^\s]+")]
[StringLength(50)]
[DataMember]
public string Route { get; set; }
[DataMember]
public ICollection<Permission> Permissions { get; set; }
[DataMember]
public Guid? ParentModuleId { get; set; }
[ForeignKey("ParentModuleId")]
[DataMember]
public virtual ICollection<Module> Children { get; set; }
}
[DataContract(IsReference = true)]
public class Permission
{
[Key]
[DataMember]
public Guid PermissionId { get; set; }
[Required]
[StringLength(100)]
[DataMember]
public string Role { get; set; }
[DataMember]
public Guid ModuleId { get; set; }
[ForeignKey("ModuleId")]
[DataMember]
public Module Module { get; set; }
}
I have a Query All function as below, which would return correctly all root with its children.
public override IQueryable<Module> All()
{
return this.Context.Set<Module>().Include(c => c.Children).Where(p => p.ParentModuleId == null);
}
Now, I want to return same list of root with its children which has the Permission "User". How do i do this. This is what i have so far. Is this correct way of doing this? please help.
return this.Context.Set<Module>().Include(c => c.Children).Where(p => p.ParentModuleId == null).Include(p => p.Permissions).Where(s => s.Permissions.Any(r=>r.Role=="User"));
btw, i have no idea how to use properly these functions such as include,where,any,select many functions. Any tutorials or books for this are appreciated. I can't find any good tutorial about this since i don't know what keyword to search for. Was this EF or LINQ.

The Include method tells Entity Framework to go populate that specific navigational property when it goes to the database to bring back the records (i.e. it eager loads the data instead of utilizing lazy loading, which would require EF to go back to the database later). It doesn't do any kind of filtering. All that is done by the 'Where` method.
To do the kind of filtering that you're asking for across all the children you'll have to do one of two things:
1) Create a Common Table Expression in SQL which will recursively grab all of the children for a specific module, place that CTE in a SQL View, map an EF entity to that View, and then query that view, including a Join to the Permissions table to grab only those that have the permission you're looking for.
2) To do this without the T-SQL fun, simply create a recursive function on your Module class with the [NotMapped] attribute over it that will go through all of the children and return only those children that have the permission you're looking for (Note: This will require more resources than the first one, and will be slower in your application, since this will be primarily a LINQ to Objects query instead of LINQ to Entities).
Something like this:
[NotMapped]
public List<Module> GrabModulesWithPermission(string permission)
{
var toReturn = new List<Module>();
if (this.Children != null && this.Children.Any(c => c.Permissions.Any(r => r.Role == permission))
{
toReturn.AddRange(this.Children.Where(c => c.Permissions.Any(r => r.Role == permission).SelectMany(c => c.GrabModulesWithPermission(permission)));
}
toReturn.Add(this);
return toReturn;
}
As far as tutorials go, I would highly recommend looking at Pluralsight. There are a number of videos there on EF, including some by Julie Lerman who is Microsoft's EF MVP.

Related

Entity framework core load navigation properties for recursive table

I am using Entity Framework Core 5.0 and the following model:
So a Job has a MainFlow, and the mainflow can have 0 or more Subflows which in turn can have subflows (recursive)
The way this has been setup is that I have a Job entity which has a MainflowId property (and also navigation property)
The Flows have a ParentFlowId property, and a navigation property with a collection of SubFlows.
I also included a TreeId property on Job and Flow to easily identify a tree.
public class Job
{
public int Id { get; set; }
public DateTimeOffset Timestamp {get; set; }
public string JobName{ get; set; }
public int MainFlowId { get; set; }
public Guid TreeId { get; set; }
public virtual Flow MainFlow { get; set; }
}
public class Flow
{
public int Id { get;set; }
public string Name { get; set; }
public string DisplayName { get; set; }
public Guid TreeId { get; set; }
public int? ParentFlowId { get; set; }
public virtual Flow ParentFlow { get; set; }
public virtual ICollection<Flow> SubFlows { get; set; } = new List<Flow>();
}
What I am trying to achieve is to load a list of jobs (for example based upon the timestamp), but in a way that all the navigation properties become available (Job->Mainflow->Subflow->Subflow->...)
I was able to get this behavior by loading the jobs, and then loading the flows separately using the TreeId, however because of performance issues I am now using .AsNoTracking() and this does not seem to work anymore.
Is there a way to load the whole trees with their navigation properties while using .AsNoTracking?
Thanks for any insight!
Edit:
Here is the way it is working without the AsNoTracking()
(I simplified the code a bit)
private IQueryable<Job> GetAllJobs()
{
return DbContext.Set<Job>()
.Include(a=>a.MainFlow)
}
private IEnumerable<Flow> GetAllFlowsForTreeIds(IEnumerable<Guid> treeIds)
{
var result = from flow in DbContext.Set<Flow>()
.Include(a => a.ParentFlow)
.AsEnumerable()
join treeId in treeIds
on flow.TreeId equals treeId
select flow;
return result;
}
public IEnumerable GetJobTrees()
{
var allJobs = GetAllJobs().ToList();
var flows = GetAllFlowsForTreeIds(allJobs.Select(a=>a.TreeId)).ToList());
//by getting the flows, the navigation properties in alljobs become available
}

Returning different versions of set of data

So I have a class with a static method that uses EF to retrieve certain set of mailing lists and maps to the class.
public static List<MailingList> GetMailingListsForUser(IUsersAccess user, IProspectorDataSource db )
{
return db.MailingLists.Where(x => x.UserID == user.UserID).ToList()
.Select(y => new MailingList(y, db) ).ToList();
}
Now though I have a proc that will return the MailingList plus some extra stuff. I don't want to add these extra columns (which will be used in other sections and areas of functionality) to this class. What is the best way to address this?
I am thinking a Factory Pattern that will generate a different class that implements different contracts (interfaces) based on whats needed. Going to try implement it and will post code/working when completed.
Was wondering what other people have done in instances like this and if there are any better ways to address this.
Edit: (some extra information to help people understand what I mean).
public class MailingList
{
public int MailingListID { get; set; }
public string Name { get; set; }
public string Comments { get; set; }
public List<string> Tags { get; set; }
public int UserID { get; set; }
public System.DateTime DateCreated { get; set; }
public string CreatedBy { get; set; }
public System.DateTime LastModified { get; set; }
public string ModifiedBy { get; set; }
public List<MailingListAddress> MailingListAddresses { get; set; }
That is the definition of an object that we return. Now there is a new instance where I am going to return some extra columns from a proc and map to MailingList. So I could just add the properties to here but the issue is MailingListAddresses will be null as they will not be returned by the stored proc. So is there a way to map to specific properties and not have to return null for MailingListAddresses to the front end every time.
This was fixed by a senior developer who ended up going with the factory pattern. I will add the code when I get back to work :)

Entity Framework returns null for Include properties

I got 3 entities(tables) that have many to many connections:
public class AccUserRole
{
public long Id { get; set; }
public string RoleName { get; set; }
public List<AccAdGroup> Groups { get; set; }
public List<AccScreen> Screens { get; set; }
}
public class AccAdGroup
{
public long Id { get; set; }
public string AdIdent { get; set; }
public List<AccUserRole> Roles { get; set; }
}
public class AccScreen
{
public long Id { get; set; }
public string ScreenIdent { get; set; }
public List<AccUserRole> Roles { get; set; }
}
I wanted to get all Roles(including their screens and groups) that has at least one of specified list of groups(the groups of the current user). So I used this query:
List<AccUserRole> userRoles = (from ur in db.AccUserRoles.Include("Groups").Include("Screens")
from g in ur.Groups
where user.Groups.Contains(g.AdIdent)
select ur).ToList();
It gets the right roles, but the Groups and Screens properties are null. Looks like EF has a problem with using Include and second from.
Any help on how to include the properties or rewrite the query will be appreciated.
Eager Loading
The reason for this is that you have specified only one level of include, while your query is asking for something on the second level.
Your include lets you ask for ur.Groups and ur.Screens.
The next level is from g in ur.Groups, and you haven't included that level. (This is probably unexpected for you, since you already have asked for all AccUserRoles in the first part of the query.)
To make your query run, you could add another .include at the start, going two levels deep:
from ur in db.AccUserRoles
.Include("Groups")
.Include("Groups.Roles")
.Include("Screens")
If you need to go yet another level, you'd just add yet another include:
from ur in db.AccUserRoles
.Include("Groups")
.Include("Groups.Roles")
.Include("Groups.Roles.Groups")
.Include("Screens")
...etc.
This might become cumbersome if you have a whole lot of levels to nest, so an alternative would be to use Lazy Loading instead, as Praval 'Shaun' Tirubeni suggests, by adding the virtual keyword to the collections in your entities.
Move the include before ToList().
select ur).Include("Groups").Include("Screens").ToList();
The subselect can remove the Include effect.
If you are doing eager loading, the virtual keyword is not needed.
By adding virtual, you are using lazy loading, not eager loading.
Try adding the virtual key word to your class properties like so:
public class AccUserRole
{
public long Id { get; set; }
public string RoleName { get; set; }
public virtual List<AccAdGroup> Groups { get; set; }
public virtual List<AccScreen> Screens { get; set; }
}
public class AccAdGroup
{
public long Id { get; set; }
public string AdIdent { get; set; }
public virtual List<AccUserRole> Roles { get; set; }
}
public class AccScreen
{
public long Id { get; set; }
public string ScreenIdent { get; set; }
public virtual List<AccUserRole> Roles { get; set; }
}

Entity Framework v5 loading relational data

I have two very simple POCO that i want to connect through a one to many relation
public class Menu
{
public int MenuId { get; set; }
public bool IsActive { get; set; }
public ICollection<MenuMember> MenuMembers { get; set; }
}
public class MenuMember
{
public int MenuMemberId { get; set; }
public int MenuId { get; set; }
public string ViewRoute { get; set; }
public bool IsActive{ get; set; }
}
public class EFDbContext : DbContext
{
public DbSet<Page> Pages { get; set; }
public DbSet<Menu > Menus { get; set; }
public DbSet<MenuMember> MenuMembers{ get; set; }
}
Now what I have to do is very simple , but I all the resources on the internet are suprisingly so vague (or i am too dumb)
I want to write an lambda expression for
SELECT *
FROM Menu INNER JOIN MenuMembers
ON Menu.MenuId = MenuMembers.MenuId
WHERE Menu.MenuId = 1
I have used this
IEnumerable<Menu> menu
= repository.Menus.Where(x => x.MenuId == menuId);
but when I iterate over it, menu.MenuNumbers stays null. I believe it is some sort of lazyloading issue.
Either Include() the relation manually for eager loading:
Entity Framework Loading Related Entities:
Eager loading is the process whereby a query for one type of entity also loads related entities as part of the query. Eager loading is achieved by use of the Include method.
IEnumerable<Menu> menu = repository.Menus
.Include(m => m.MenuMembers)
.Where(x => x.MenuId == menuId);
Or mark the property as virtual so Entity Framework will lazy-load it:
Lazy loading is the process whereby an entity or collection of entities is automatically loaded from the database the first time that a property referring to the entity/entities is accessed. When using POCO entity types, lazy loading is achieved by creating instances of derived proxy types and then overriding virtual properties to add the loading hook.
public class Menu
{
public int MenuId { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<MenuMember> MenuMembers { get; set; }
}
And there's a few other options, be sure to check out the documentation.
You need to declare MenuMembers as virtual
public virtual ICollection<MenuMember> MenuMembers { get; set; }

Updating FK relationships in Entity Framework 4.1

I think I have read every article and stack overflow question regarding this, but cannot work out the solution. Let me start out with my models
public class Entry
{
public Entry ()
{
DateEntered = DateTime.Now;
}
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
public string Number { get; set; }
public string FbId { get; set; }
[ReadOnly(true)]
public DateTime DateEntered { get; set; }
public string AccessToken { get; set; }
//Relationsips
public Backgrounds Background { get; set; }
public Cars Car { get; set; }
}
public class Backgrounds
{
public int Id { get; set; }
public string Name { get; set; }
public string Filename { get; set; }
}
public class Cars
{
public int Id { get; set; }
public string Name { get; set; }
public string FileName { get; set; }
}
Now in my controller, I am updating the entry. Like follows
// PUT /api/entries/5
public HttpResponseMessage Put(Entry entry)
{
if(ModelState.IsValid)
{
_db.Entries.Attach(entry);
_db.Entry(entry).State = EntityState.Modified;
_db.SaveChanges();
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
My Entry model gets updated correctly, but if for eg entry.Background.Name changes, this will not be persisted to the database. My controller is accepting the entire entry model including its relationships => Backgrounds and Cars. However any value that is changed to the relationship is not updated or reflected. Any elegant solution without having to query the database then updating? I dont want to have any extra queries or lookups before I update.
Thanks
Tyrone
You must manually tell EF about all changes done to the object graph. You told EF just about change to entry instance but you didn't tell it about any change to related entities or relations itself. There is no elegant way to solve this. You have generally two options:
You will use some DTOs instead your entities and these DTOs will have some flag like IsDirty - when you receive object graph back to your controller you will reconstruct entities from DTOs and set their state based on IsDirty. This solution needs further extensions for example if your client can also delete relations.
You will query object graph from database and merge your incoming changes to entities retrieved from database.
There are some partial solutions like forcing to save changes to all related objects by setting their state to modified and identifying new objects by Id == 0 but again these solutions work only in specific scenarios.
More complex discussion about this problem.

Categories