Use LINQ to flatten tree getting deepest value - c#

If I have a hierarchical structure of departments and employees, like this:
public class TestEmployee
{
public Guid ID { get; set; }
public Guid TestDepartmemntID { get; set; }
public string name { get; set; }
public string email { get; set; }
}
public class TestDepartmemnt
{
public Guid ID { get; set; }
public Guid ParentTestDepartmentID { get; set; }
public string TestDepartmentName { get; set; }
public string? ManagerName { get; set; }
public string phoneNumber { get; set; }
}
Noting that ManagerName is nullable...
Is there a more elegant way to get employee's direct managername other than iterating up ancestors to find the closest department manager name?
If Widgets Inc has CEO Bob at the root, and Bob manages several departments, one of which is Logistics.
Logistics has several departments, one is Warehouse. Warehouse has a manager of Sharon.
The Warehouse has multiple departments, including shipping and receiving.
Rick works in shipping.
Rick's manager is Sharon.
I can easily determine which department Rick works in because I can get the TestDepartmentID.
However, when getting Rick out of the tree, do I have to get do something like:
Get Ricks TestDepartmentID's closest ancestors where ManagerName is not null or empty?
Is there a more elegant way to do this than a recursive loop?

Linq operates on IEnumerables, so the first step would be to create one. For example by using an iterator block. You can then use .First() or any other linq methods to return the first non-null manager, or any other query you want.
public Dictionary<Guid, TestDepartmemnt> departments = new Dictionary<Guid, TestDepartmemnt>();
public IEnumerable<TestDepartmemnt> GetParents(Guid departmentId)
{
while(departments.TryGetValue(departmentId, out var department))
{
yield return department;
departmentId = department.ParentTestDepartmentID;
}
}
This assumes you have the departments in a dictionary. This can be replaced with whatever method you use to link ids to departments.

Related

How should I order nested entities when I load parent entity?

I have entities similar to this:
public class Search
{
public int ID { get; set; }
public bool Complete { get; set; }
public int Processed { get; set; }
public virtual ICollection<PostCode> PostCodes { get; set; }
}
public class PostCode
{
public int ID { get; set; }
public string Value { get; set; }
}
I want to make sure that the PostCode collection is always ordered by its ID field when I load a Search entity.
Is there a way to set an order for nested collection when I load it?
There is many thousand postcodes so it is expensive process to iterate over whole list. In the below code am I iterating over every postcode twice or is this the right way to do it?
Search s = db.Searches.Include("PostCodes").FirstOrDefault(x => x.Complete == false);
s.PostCodes = s.PostCodes.OrderBy(x => x.ID).ToList();
I haven't profiled it but it feels like it's taking longer now.

Grouping hierarchical data with LINQ

I have a search view that needs to return a grouping of all employees based on their level in the organization hierarchy. The user needs to have the ability to group by any level in this hierarchy. So for example, I have drop down check lists that allow 1 or more options to be selected for Division, Department, Section, Group.
The way that I have the organization structure represented in my data model is like this:
public class OrganizationEntity : IEntity
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public int OrganizationEntityTypeId { get; set; }
[DataMember]
[ForeignKey("OrganizationEntityTypeId")]
public virtual OrganizationEntityType OrganizationEntityType { get; set; }
[DataMember]
public virtual OrganizationEntity Parent { get; set; }
public virtual ICollection<OrganizationEntity> Children { get; set; }
}
The OrganizationEntityType tells me where in the organization hierarchy I am.
Employees are only linked to one organization entity so employee model looks like:
public class Employee : IEntity
{
[Key]
[DataMember]
public int Id { get; set; }
[Required]
[DataMember]
public string Name { get; set; }
[DataMember]
public int CityId { get; set; }
[DataMember]
[ForeignKey("CityId")]
public virtual City City { get; set; }
[DataMember]
public int OrganizationEntityId { get; set; }
[DataMember]
[ForeignKey("OrganizationEntityId")]
public virtual OrganizationEntity OrganizationEntity { get; set; }
}
Now I'm trying to figure out the best way to group by a level in the org hierarchy using LINQ. So say the user selects Division 1, Division 2, All Sections, Group 1 and has their grouping set to Divisions I would need to see the data looks something like this:
Divisions Employee Count
division1 25
division2 3
And if the same parameters where the same except the grouping was set to Section the data would look like:
Sections Employee Count
Section1 15
Section2 3
Section3 4
And so on for the other levels.
Here is a sample dataset:
Any advice would be very much appreciated.
Update
Added a filter to the employees query to only include those in the Org level being group by and all of those below it and now works great! Code looks like this:
var employees = context.Employees.Where(o => orgEntityIds.Contains(o.OrganizationEntityId));
LINQ can do this with the GroupBy method, but you'll have to be a bit creative with the way you generate the grouping key. Here's a rough idea:
Func<Employee, string> selector = (n => FindOrganizationName(n.OrganizationEntity, orgType));
Dictionary<string, int> results = employees.GroupBy(selector)
.ToDictionary(n => n.Key, n => n.Count());
where FindOrganizationName is a recursive function that crawls up the organization hierarchy until it finds the requested organization type, like this:
string FindOrganizationName(OrganizationEntity entity, int entityType)
{
if (entity == null)
{
return string.Empty;
}
else if (entity.OrganizationEntityTypeId == entityType)
{
return entity.Name;
}
else
{
return FindOrganizationName(entity.Parent, entityType);
}
}

ASP.NET EF6 Query Model

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.

Retrieve a list of items where an item exists in one of the items lists

Please excuse the slightly confusing title. I have a model (Project) which contains a list of items (Users).
I would like to retrieve all of the projects, where the current user is a member of the user list for that project.
I've tried:
List<Project> _MemberProjects =
_Db.Projects.Where(p =>
p.Users.Contains(_User)
).ToList();
This results in the following error:
Unable to create a constant value of type 'Nimble.Models.UserAccount'. Only primitive types or enumeration types are supported in this context.
User Model:
public class UserAccount
{
public int UserID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public ICollection<Project> Projects{....}
}
Project Model
public class Project
{
public int ProjectID { get; set; }
public DateTime CreatedDate { get; set; }
public string ProjectName { get; set; }
public string Owner { get; set; }
public ICollection<UserAccount> Users{...}
public ICollection<ProjectGroup> Groups{...}
}
Haven't tried this, but it might work:
List<Project> _MemberProjects =
_Db.Projects.Where(p =>
p.Users.Any(u => u.UserID == _User.UserID )
).ToList();
The problem is that you are mixing together Linq (the WHERE clause) and a non-Linq Collection operation (Contains). Try using pure Linq. #JamesBond's answer might work.
Are you querying a database? Then a JOIN might be another solution, but the exact syntax depends on how you are storing the relationship between the two tables.

MVC - Linq - Populate List<T> with Records in Another Table

I'm prototyping my first MVC application, it's a simple forum. I've done part of the domain model and I'm trying to figure out how to do something that's pretty basic in SQL alone, but I can't figure it out in my application. Here are my Entities:
[Table(Name="Users")]
public class User
{
[Column(IsPrimaryKey=true, IsDbGenerated=true, AutoSync=AutoSync.OnInsert)]
public int Id { get; set; }
[Column] public string Username { get; set; }
[Column] public string Password { get; set; }
[Column] public string PasswordHash { get; set; }
[Column] public string PasswordSalt { get; set; }
[Column] public string FirstName { get; set; }
[Column] public string LastName { get; set; }
public List<Forum> AllowedForums { get; set; }
[Column] public DateTime LastLogin { get; set; }
[Column] public DateTime MemberSince { get; set; }
}
[Table(Name="Forums")]
public class Forum
{
[Column(IsPrimaryKey=true, IsDbGenerated=true, AutoSync=AutoSync.OnInsert)]
public int Id { get; set; }
[Column] public int ParentId { get; set; }
[Column] public string Title { get; set; }
[Column] public string Description { get; set; }
[Column] public bool IsGlobal { get; set; }
[Column] public int DisplayOrder { get; set; }
}
I also have a linking table called AllowedForums that looks like:
userid forumid
1 4
In order to select the forums that a user is allowed to view and forums where IsGlobal == true I'd do this in SQL:
SELECT * FROM Forums
LEFT OUTER JOIN AllowedForums ON Forums.id = AllowedForums.Forumid
WHERE AllowedForums.Userid = 1
OR Forums.IsGlobal = 1
How should I populate the
public List<Forum> AllowedForums
field using C#/Linq to SQL?
Should AllowedForum be a value object with its own table mapping? That seems like overkill but I could easily join on it. I looked briefly at EntitySet but the simple example I saw didn't seem to fit. It feels like there should be an elegant way to get a collection of Forum objects for each User, but I can't come up with any. BTW, I'm new to C# & OO. I should also mention that since these are the early stages of the app, I'm open to changing the structure/relationships of the entities or tables if there's a better approach I'm not seeing.
You should have another Entity class (probably should be internal) that mirrors your AllowedForums table in the database. Now I'm assuming your User table and your Forums table both have PK/FK relationships to this AllowedForums table. Therefore, there should be an internal property on the User class that looks like this:
internal EntitySet<AllowedForums> AllowedForumsRelationships
{
get;set;
}
Or something like that. This should be on both the User and Forums class. Your AllowedForums class will have two properties on it. One for User and one for Forum. (If you use the LINQ to SQL designer, all this will happen for you automatically).
Once you have that, if you want to get all the AllowedForums for a user you can do something like this:
public IList<Forum> AllowedForums
{
get
{
var result = new List<Forum>();
foreach(var relationShip in this.AllowedForumsRelationships)
{
result.Add(relationShip.Forum);
return result;
}
}
}
This is some rough code I just banged out, and I'm not sure it's 100% accurate, but I think you'll get the idea. Basically you're dealing with a many to many relationship which is always a pain.
EDIT: I just messed with this idea with the Northwind Database with these tables:
Orders
OrderDetails
Products
There's a many to many relationship there: An order can have multiple products, and a product can belong to many orders. Now say you want to get all products for an order:
public partial class Order
{
public IList<Product> Products
{
get
{
var list = new List<Product>();
foreach (var item in this.Order_Details)
{
list.Add(item.Product);
}
return list;
}
}
}
That works, so it should work in the scenario you're talking about as well.
Something like:
var AllowedForums = from f in ForumsTable
join af in AllowedForumsTable on f.Id equals af.forumid into temp
from aft in temp.DefaultIfEmpty()
where (f.IsGlobal == 1 || aft.userid == 1)
select f;

Categories