I have an employee class that has an employeeId (int), parent(int) and children property List<Employee>. I get the employee list from the database in the correct order and now need to build the hierarchy, but I am failing miserably...I know this is programming 101, but I am having a hard time with it.
public class Employee
{
public int EmployeeId { get; set;}
public int ParentId;{ get; set;}
public List<Employee> Children; { get; set;}
}
Data Example
EmployeeId, ManagerId
1, 0 //no one
2, 1
3, 1
4, 2
5, 2
6, 3
7, 3
List<Employee> allEmployees = new List<Employee>();
allEmployees.AddRange(LoadAllEmployees()); // pull from DB in flat format
foreach (var employee in allEmployees)
{
employee.Children = allEmployees.Where(e => e.ParentId == employee.EmployeeId).ToList();
}
You can start by creating a list of all the employee objects and setting the EmployeeId and ParentId properties. If you also put them in a dictionary, keyed by EmployeeId, you can retrieve the parent of each afterward to add to the Children collection:
List<Employee> employees = new List<Employee>();
Dictionary<int,Employee> dict = new Dictionary<int,Employee>();
foreach(result from database query)
{
Employee employee = new Employee();
employee.EmployeeId = result["EmployeeId"];
employee.ParentId = result["ParentId"];
employees.Add(employee);
dict.Add(employee.EmployeeId, employee);
}
foreach(Employee e in employees)
{
dict[e.ParentId].Children.Add(e);
}
i got inspiration from this article a while ago (i had to change it slightly to suit my purposes). It basically builds a hierarchical structure to the n'th degree.
Might be useful, even if only to discount its approach in your own case :-)
http://www.scip.be/index.php?Page=ArticlesNET23&Lang=EN
Related
This is my first dip into Entity Framework - as a long time Linq2Sql guy I never had anyone show me what EF did better. I’ve followed some MS tutorials and I have a context that creates a database and stores stuff easily. I can query it with LINQ so it’s still familiar.
What isn’t working though is a relationship. I have three classes, call them Product, Place, and Price. They all have an int Id. Product and Place both have a virtual icollection, and Price has a property each for Product and Place.
The logical model is that I have a list of products that are sold in zero or more places, and any product might have a different price in each location (think local tax adjustments).
My first test wouldn’t populate the list of prices from the product, so myProduct.Prices was null. I worked around that by getting the list of prices myself
var prices = dB.Prices.Where(...)
My problem is when I have more than one Place (Id 1 and 2), and I put in data for multiple prices, the Place is null where the Id is 2.
I have tried adding data directly to the tables (a place with Id 2, anD a price with Place_Id = 2). I have tried adding them with code.
If I go in to the Price table and change both Place_Id to 1, it works in that they both retrieve Place 1. If I set them both to two they both give me null Place. If I set one each way, the one with 1 works and the other is null.
So my relationship works but only when the FK is 1. WTF have I done?
edit: code samples (sorry, I was on my phone earlier)
{ //declared in my dbcontext
...
public DbSet<Product> Products { get; set; }
public DbSet<Place> Places{ get; set; }
public DbSet<Price> Prices{ get; set; }
...
}
//Just to be clear, below are separate from the dbcontext
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
...
[InverseProperty("Product")]
public ICollection<Price> Prices { get; set; }
}
public class Place
{
public int Id { get; set; }
public string Name { get; set; }
...
[InverseProperty("Place")]
public ICollection<Price> Prices { get; set; }
}
public class Price{
public int Id { get; set; }
public Product Product { get; set; }
public Place Place { get; set; }
...
}
I tried inserting rows directly, also I tried
//In the Seed(Context) method of my DbContextInitializer, where db is an instance of my dbcontext
var a1 = new Place { Name = "Place 1"};
var a2 = new Place { Name = "Place 2"};
var p = new Product { Name = "Test Product" };
db.Products.Add(p);
db.Associations.Add(a1);
db.Associations.Add(a2);
db.Prices.Add(new Price { Amount = 10.1m, Place= a1, Product = p });
db.Prices.Add(new Price { Amount = 3.45m, Place= a2, Product = p });
db.SaveChanges();
All of that data gets inserted and I can interrogate the database to see it.
The code that is coming unstuck is:
foreach (var p in db.Products.ToList()) //NB if I try this without the ToList I get an exception about an open data reader
{
var price = 0m;
foreach (var o in db.Prices)
{
//there are two price records which we created above.
//In the first Price, o = {Id = 1, Place_Id = 1, Product_Id = 1}. This works.
//In the second Price, o = {Id = 2, Place_Id = 2, Product_Id = 1}. o.Place is null
// Yes, there is absolutely a Place {Name = "Place 2", Id = 2} in the database
if (o.Place.Id == ...)price += o.Amount;
}
...
}
Something interesting I noticed. If I jam in more products, it works with any product Id. Also (and I suspect this is the underlying issue), I notice that o.Product is of type Product, however o.Place is of type DynamicProxies.Place_Guid - but I'm not understanding why these are different I have declared the properties in identical fashion as you can see above.
var results = (from bk in bookContext.Books
orderby bk.id descending
select bk).ToList();
The table "Book" also has a genre id and if a user subscribes into a specific genre, when listing suggested books I want to order by those subscribed genres first and then order by book id. I can get the list of subscribed genre ids as a list below.
List<int> subGenereIds = new List<int>() { 10, 12, 22 };
How can add the order by section to the initial Linq query above so that users get books that have genre id in list first and the rest after that?
You'll need to to the ordering in-memory since there's not a way to pass that ordering logic to SQL. You can do that by using AsEnumerable() to change the context from a SQL query to an in-memory query:
var results = bookContext.Books
.AsEnumerable()
.OrderByDescending(bk => bk.Id)
.ThenBy(bk => subGenereIds.IndexOf(bk.Id));
From your comments it looks like you need to have as much of this as possible done in the the database. Assuming that you have the books and the user subscriptions stored in the same place you can combine the query for the user's preferences with the search of the book table to get the result you're after.
For the sake of argument, let's assume you have some tables that look something like this as POCOs:
[Table("Books")]
public class Book
{
public int ID { get; set; }
public int Genre { get; set; }
public string Name { get; set; }
public string Author { get; set; }
public Date PublishDate { get; set; }
}
[Table("Genres")]
public class Genre
{
public int ID { get; set; }
public string Name { get; set; }
}
[Table("Users")]
public class User
{
public int ID { get; set; }
public string Name { get; set; }
}
[Table("UserGenrePreferences")]
public class UserGenrePreference
{
public int User { get; set; }
public int Genre { get; set; }
}
For the sake of the example we have one genre per book and no duplicates in the UserGenrePreferences table.
The basic ordering might look something like this:
// get genre preferences for the selected user
var userPrefs =
from pref in context.UserGenrePReferences
where pref.User == userID
select new { Genre = (int?)pref.Genre };
var result =
from book in context.Books
join pref in userPrefs on book.Genre equals pref.Genre
into prefTemp
from pref in prefTemp.DefaultIfEmpty()
orderby (pref.Genre == null ? 1 : 0), book.ID
select book;
This does an outer join against the user's genre preferences. Books with a matching genre will have a non-null value in the matching pref record, so they will be placed before unmatched books in the result. Pagination would then by handled by the normal .Skip(...).Take(...) method.
For very large data sets this will still take some time to run. If you're expecting that the user will be paging through the data it might be a good idea to grab just the book IDs and cache those in memory. Take the first few thousand results and look for more when needed for instance. Then grab the book records when the user wants them, which will be much quicker than running the query above for every page.
If on the other hand your user preference data is not in the same database, you might be able to use the Contains method to do your ordering.
Assuming that you have an array of genre IDs in memory only you should be able to do this:
int[] userPrefs = new int[] { 1, 2, 3, 5 };
var result =
from book in context.Books
orderby (userPrefs.Contains(book.Genre) ? 0 : 1), book.ID
select book;
Depending on the ORM this will translate into something similar to:
SELECT *
FROM Books
ORDER BY
CASE WHEN Genre = 1 OR Genre = 2 OR Genre = 3 OR Genre = 5 THEN 0 ELSE 1 END,
ID
This can be fairly quick but if the list of preferences is very large it could be slower.
I don't recommend that you write the more complex linq query to solve it.
Yes, it's becoming shorter that way but also becoming harder to read. The longer answer below. You can shorten it by yourself.
First you should get the ones which have genreIds of subGenereIds list.
var onesWithGenreIdFromList = (from bk in bookContext.Books
where subGenereIds.Contains(bk.genreId).orderby bk.Id descending select bk).ToList();
Then get the rest of the list (ones don't have genreId of subGenereIds list) from your list.
var results = (from bk in bookContext.Books
where !subGenereIds.Contains(bk.genreId)
orderby bk.id descending
select bk).ToList();
As the last step you should add the first list to second one to create the order you would like:
onesWithGenreIdFromList.AddRange(results);
var lastResults = onesWithGenreIdFromList;
Happy coding!
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
Hey Everyone, I am mentally stuck
I have a list of objects retrieved from a Web API that has three values that consist of a parent ID and a string value and a row ID
IE:
CategoryID Name ParentID
1 Tools 0
2 Hammer 1
3 ScrewDriver 1
4 Phillips 3
5 Standard 3
6 #2 4
7 Torx 3
8 #15 7
etc.
This needs to be put into a simple list object that consists of a ParentID and a concatenated string of the immediate category name and the parent id
CategoryID FullCategoryName
0 Tools
2 Tools/Hammer
3 Tools/ScrewDriver
4 Tools/ScrewDriver/Phillips
5 Tools/ScrewDriver/Standard
6 Tools/ScrewDriver/Phillips/#2
7 Tools/ScrewDriver/Torx
8 Tools/ScrewDriver/Torx/#15
As I hope you are able to see is that I need the categoryID and the full path based off the parentID with a slash.
API Called Class
public class Categories_Web
{
public string CategoryName { get; set; }
public int CategoryParent { get; set; }
public int CategoryID { get; set; }
}
Simplified Class with concatenated names
public class WebCategoriesSimple
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
}
I hope that this makes sense and thanks for your help!
What you have is a hierarchy, and whenever you have that you can consider Recursion - a method that calls itself. You don't always want to use recursion, but for manageable sized lists or tail call optimized recursive methods it is a powerful tool.
Here is demo code that outputs:
Category: 1, Hierarchy: Tools
Category: 2, Hierarchy: Tools/Hammer
Category: 3, Hierarchy: Tools/ScrewDriver
Category: 4, Hierarchy: Tools/ScrewDriver/Phillips
Category: 5, Hierarchy: Tools/ScrewDriver/Standard
Category: 6, Hierarchy: Tools/ScrewDriver/Phillips/#2
Category: 7, Hierarchy: Tools/ScrewDriver/Torx
Category: 8, Hierarchy: Tools/ScrewDriver/Torx/#15
(Note, I don't think your sample output of "0, Tools" is correct)
This program creates a hard-coded list of ProductDef. Yours probably comes from a database or something.
Then it creates an empty list of ProductHierarchy which will be populated as the recursive operation runs.
It then kicks off the recursion with the first call to Build(). Within that method, it will call itself while the item passed in has a parent in the hierarchy.
When there are no more parents, it adds the item to the list of ProductHierarchy.
void Main()
{
List<ProductDef> pdefs = new List<UserQuery.ProductDef>{
new ProductDef{Category = 1, Product = "Tools", ParentCategory = 0},
new ProductDef{Category = 2, Product = "Hammer", ParentCategory = 1},
new ProductDef{Category = 3, Product = "ScrewDriver", ParentCategory = 1},
new ProductDef{Category = 4, Product = "Phillips", ParentCategory = 3},
new ProductDef{Category = 5, Product = "Standard", ParentCategory = 3},
new ProductDef{Category = 6, Product = "#2", ParentCategory = 4},
new ProductDef{Category = 7, Product = "Torx", ParentCategory = 3},
new ProductDef{Category = 8, Product = "#15", ParentCategory = 7}
};
//This will get filled as we go
List<ProductHierarchy> phlist = new List<UserQuery.ProductHierarchy>();
//kick off the recursion
foreach (var element in pdefs)
{
Build(element, pdefs, element.Product, element.Category, phlist);
}
//do stuff with your list
foreach (ProductHierarchy ph in phlist)
{
Console.WriteLine(ph.ToString());
}
}
class ProductDef
{
public int Category { get; set; }
public string Product { get; set; }
public int ParentCategory { get; set; }
}
class ProductHierarchy
{
public int Category { get; set; }
public string Hierarchy { get; set; }
public override string ToString()
{
return $"Category: {Category}, Hierarchy: {Hierarchy}";
}
}
void Build(ProductDef def, List<ProductDef> lst, string fullname, int cat, List<ProductHierarchy> phlist)
{
string fullprodname = fullname;
//obtain the parent category of product
var parent = lst.FirstOrDefault(l => l.Category == def.ParentCategory);
if (parent != null)
{
fullprodname = $"{parent.Product}/{fullprodname}";
//recurse the call to see if the parent has any parents
Build(parent, lst, fullprodname, cat, phlist);
}
else
{
//No further parents found, add it to a list
phlist.Add( new ProductHierarchy { Category = cat, Hierarchy = fullprodname });
}
}
This is one of those problems that I think LINQ is great for. I'm having a bit of trouble understanding the format of your data. You can use a projection with LINQ, though, to project the results of the original list into a new list containing either a new object based on a class definition or a new anonymous class.
Projecting into a new anonymous class, using LINQ, would look something like the following. Keep in mind that we don't know your object names, because you didn't provide that information, so you may need to extrapolate a bit from my example.
(Note that I reread your post and I think I understand what your original objects may be called, so the post has been edited to use your class names.)
var newListOfStuff = originalListOfStuff.Select(s => new {
CategoryID = s.CategoryID,
CategoryName = $"Tools/{s.CategoryName}/#{s.CategoryParent}"
});
The string is created using string interpolation, which is new in C#. If your version of C# doesn't support that, you can use string.Format instead, like this:
string.Format("Tools/{0}/#{1}", s.CategoryName, s.CategoryParent);
With that new list, you can loop through it and use properties like this:
foreach(var item in newListOfStuff)
{
Console.WriteLine(item.CategoryID);
Console.WriteLine(item.CategoryName);
}
Here is a similar answer for references:
https://stackoverflow.com/a/9885766/3096683
I got this application that reads the employee data from the database, only problem the boss of an employee is set in employee_id and not in name, which is what I am looking for.
I got a List filled with all the employees. So I am looking for a way to query it with LINQ through the list to link the employee_id to the Firstname/Lastname.
Example: The employee with the ID 1 is Nancy and her boss is Andrew, but it doesn't say Andrew it says 2. But it should say Andrew
I also added 2 images below to add to my explanation.
Listview of the employees
Reading out the list of employees into the ListView
First, load the employees into some local variable (you'll need it later):
List<Employee> employees = Database.getEmployees();
Then edit your foreach cycle:
// 'employee' is better because here it is really just one specific employee
foreach (Employee employee in employees)
Now you can get the name of the boss like this (in foreach cycle):
string boss = employees.FirstOrDefault(x => x.ID == employee.ReportsTo)?.FirstName;
(You need at least C# 6.0 for the ? operator.)
So you need to Left Join ID with Boss and get the Boss Info if found:
var employees = Database.getEmployees();
var employeesWithBoss = (from e in employees
join b in employees
on e.ID equals b.Boss into leftJoin
from boss in leftJoin.DefaultIfEmpty()
select new
{
Employee = e,
BossFirstName = boss == null ? null : boss.FirstName,
BossLastName = boss == null ? null : boss.LastName
}).ToList();
foreach (var employee in employeesWithBoss)
{
// do your normal work here, you now
// have employee.BossFirstName and employee.BossLastName
}
you can use lambda to achieve this.
class Program
{
static void Main(string[] args)
{
List<Employee> employees = new List<Employee>();
employees.Add(new Employee() { EmployeeName = "Nancy", EmployeeId = 1, BossId = 2 });
employees.Add(new Employee() { EmployeeName = "Andrew", EmployeeId = 2, BossId = 0 });
employees.Add(new Employee() { EmployeeName = "Janet", EmployeeId = 1, BossId = 2 });
var employeesWithBossName = employees.Join(employees,
emp1 => emp1.BossId,
emp2 => emp2.EmployeeId,
(emp1, emp2) => new { EmployeeName = emp1.EmployeeName, BossName = emp2.EmployeeName });
foreach (var item in employeesWithBossName)
{
Console.WriteLine("{0} {1}", item.EmployeeName, item.BossName);
}
Console.Read();
}
}
public class Employee
{
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public int BossId { get; set; }
}
Hope this will help.
I am new to LINQ and sorry if my question have been asked
I have 2 classes
public class Person
{
int ID {get;set;}
string FirstName {get;set;}
string LastName {get;set;}
}
and
public class House
{
int ID {get;set;}
string Address {get;set;}
string ZipCode {get;set;}
int PersonId {get;set;}
}
I am saving the list of houses in a IEnumerable List
IEnumerable<House> ListHouses = GetAllHouses();
GetAllHouses return the list of houses from the database
I want to use Lamda select in LINQ in order to do the following
var st = ListHouses .Select(h => new
{
id = h.ID,
Address= h.Address,
Zip= h.ZipCode ,
PersonFirstName = GetPersonByID(h.PersonId ).FirstName,
PersonLastname = GetPersonByID(h.PersonId ).lastname
});
Where GetPersonByID returns an object of Type Person that has the given ID. and then I take his first name and last name.
My question is this:
Instead of Getting the Person 2 times for the variables (personFirstName and PersonLastName) Is there a way I can get it one time and then used it. Something like
PersonForId = GetPersonByID(h.PersonId)
PersonFirstName = PersonLastName.FirstName,
PersonLastname = PersonLastName.lastname
I'm looking for something similar to Join in SQL where you join a value from another table.
Thanks you very much for any help
You're extremely close! Using your code (and making all properties on House and Person public), here is a method using the LINQ Join method:
var st = GetAllHouses().Join(GetAllPersons(),
outerKey => outerKey.PersonId,
innerKey => innerKey.ID,
(house, person) => new
{
house.ID,
house.Address,
house.ZipCode,
PersonFirstName = person.FirstName,
PersonLastname = person.LastName
});
Note: I would recommend the GetAllPersons() and the GetAllHouses() methods return IQueryable rather than IEnumerable. Doing so will build the expression (including the join), which means LINQ-to-SQL (or Entities) will build a proper SQL statement with the JOIN included, instead of enumerating the collections and then joining.
Additional information on such can be found here: Returning IEnumerable<T> vs. IQueryable<T>
using System;
using System.Linq;
class Customer
{
public int ID { get; set; }
public string Name { get; set; }
}
class Order
{
public int ID { get; set; }
public string Product { get; set; }
}
class Program
{
static void Main()
{
// Example customers.
var customers = new Customer[]
{
new Customer{ID = 5, Name = "Sam"},
new Customer{ID = 6, Name = "Dave"},
new Customer{ID = 7, Name = "Julia"},
new Customer{ID = 8, Name = "Sue"}
};
// Example orders.
var orders = new Order[]
{
new Order{ID = 5, Product = "Book"},
new Order{ID = 6, Product = "Game"},
new Order{ID = 7, Product = "Computer"},
new Order{ID = 8, Product = "Shirt"}
};
// Join on the ID properties.
var query = from c in customers
join o in orders on c.ID equals o.ID
select new { c.Name, o.Product };
// Display joined groups.
foreach (var group in query)
{
Console.WriteLine("{0} bought {1}", group.Name, group.Product);
}
}
}
Output
Sam bought Book
Dave bought Game
Julia bought Computer
Sue bought Shirt