Recursive self join - c#

I have table as below. I need to get all the manager id's for the user id provided.
userid managerid
10 1
9 10
6 9
2 6
4 1
If i pass 2 to my method I need to get 1,10,9 and 6. I have written the below query which will return only first level parent. I.e it will return only 6 & 9.
public List<int?> mymethod (int userId){
return (from e in mycontext.EmployeeManagers
join e1 in m_context.EmployeeManagers
on e.UserId equals e1.ManagerId
where e1.UserId == userId
select e1.ManagerId).AsQueryable().ToList()
}
How can I modify the query to return all the manager hirerachy?
please help.

You can not do this in a sinqle LINQ expression. You have to run this in a loop.
A better option is to do this in the database and then return the results to LINQ.
See:
Hierarchical data in Linq - options and performance
Fill a Recursive Data Structure from a Self-Referential Database Table
Linq-to-Sql: recursively get children

I would simply run a short loop like this (sorry for invalid capitalization, coded from scratch):
public List<Int> GetAllManagers(int userID)
{
var result = new List<int>();
int index = 0;
result.add(userID); // start with user (it will be removed later)
while (index < result.count)
{
var moreData = from e in mycontext.EmployeeManagers
where e.UserId == result[index];
select e.ManagerId;
foreach (int id in moreData)
if (result.indexOf(id)==-1)
result.add(id);
index++;
}
result.delete(0);
return result;
}
or recursevly
private void AddUniqueIds (List<int> elements, ref List<int> list)
{
foreach (int id in elements)
if (list.indexOf(id)==-1)
list.add(id);
}
public List<int> GetAllManagers(int userID)
{
var result = new List<int>();
var moreData = from e in mycontext.EmployeeManagers
where e.UserId == result[index];
select e.ManagerId;
foreach (int id in moreData)
AddUniqueIds(result, GetAllManagers(id));
return result;
}

You need to use different pattern.
Lets see that you get your query.
var query = myContext.EmployeeManagers
Then, you could join it however you want to
for(int = 0; i < 5; i++)
{
query = query.Join( ... ..., i, ... ); // Can't recall all
// the parameters right now.
}
And then just execute it:
var result = query.ToList();

Related

What is best and rapid way for calculate this query?

I'm beginner in c# and linq ,write this query in c#:
var query1 = (from p in behzad.Customer_Care_Database_Analysis_Centers
select p).ToArray();
for (Int64 i = 0; i < query1.Count(); i++)
{
var query2 = (from tt in behzad.Customer_Care_Database_Analysis_DETAILs
where tt.fileid == FILE_ID && tt.code_markaz ==query1[i].code_markaz //"1215" //query1[i].code_markaz.ToString().Trim() //&& tt.code_markaz.ToString().Trim() == query1[i].code_markaz.ToString().Trim()
select new
{
tt.id
}).ToArray();
if (query2.Count() > 0)
{
series1.Points.Add(new SeriesPoint(query1[i].name_markaz, new double[] { query2.Count() }));
counter += 15;
}
}//end for
but up code is very slow,i have about 1000000 Customer_Care_Database_Analysis_Centers and about 20 million record into the Customer_Care_Database_Analysis_DETAILs table,which is best query for up code?thanks.
Your current code first gets a lot of records into memory, then executes a new query for each record - where you only use the count of items, even though you again get everything.
I think (untested) that the following will perform better:
var query = from center in behzad.Customer_Care_Database_Analysis_Centers
join details in behzad.Customer_Care_Database_Analysis_DETAILs
on center.code_markaz equals details.code_markaz
where details.fileid == FILE_ID
where details.Any()
select new { Name = center.name_markaz, Count = details.Count()};
foreach(var point in query)
{
series1.Points.Add(new SeriesPoint(point.Name, new double[] { point.Count };
counter += 15;
}
Instead of a lot of queries, execute just one query that will get just the data needed
Instead of getting everything into memory first (with ToArray()), loop through it as it arrives - this saves a lot of memory

How to select from DataTable with join to same table?

I have DataTable object, which holds some "tree data structure". Data is not stored in any database, I just use DataTable to manipulate data without SQL server.
My data looks like this (indents are only for better reading here):
DataTable dtCategories = GetCategoriesAsDataTable();
id name parentId
int string int
----------------------
1 One 0
2 OneA 1
3 OneB 1
4 Two 0
5 TwoA 4
6 TwoB 4
7 TwoAA 5
8 TwoAB 5
So far - I was thinking about selecting first level with "where parentId = 0" and putting this to separate DataTable, like this:
DataTable dtFirstLevel = dtCategories.Select("[parentId] = 0");
// and after this - create DataTable for second level
// but I don't know how can I use "IN" clause here
DataTable dtSecondLevel = dtCategories.Select(?????????);
How can I select only first 2 levels of tree?
How can I select this without SQL server (by using only data objects)?
Maybe this helps:
var rows = table.AsEnumerable();
var parents = rows.Where(r => !r.Field<int?>("parentId").HasValue);
var children = rows.Where(r => r.Field<int?>("parentId").HasValue);
var secondLevel = from parent in parents
join child in children
on parent.Field<int>("id") equals child.Field<int?>("parentId").Value
select child;
var both = parents.Concat(secondLevel).CopyToDataTable();
Note that i've used Nullable<int> instead of 0 for a parent since that is more readable and less prone of errors. Here is your sample data:
var table = new DataTable();
table.Columns.Add("id", typeof(int));
table.Columns.Add("name", typeof(string));
table.Columns.Add("parentId", typeof(int));
table.Rows.Add(1, "One", (int?)null);
table.Rows.Add(2, "OneA", 1);
table.Rows.Add(3, "OneB", 1);
table.Rows.Add(4, "Two", (int?)null);
table.Rows.Add(5, "TwoA", 4);
table.Rows.Add(6, "TwoB", 4);
table.Rows.Add(7, "TwoAA", 5);
table.Rows.Add(8, "TwoAB", 5);
Result:
1 One
4 Two
2 OneA 1
3 OneB 1
5 TwoA 4
6 TwoB 4
Since you want to stay with 0 instead of int?:
var parents = rows.Where(r => r.Field<int>("parentId") == 0);
var children = rows.Where(r => r.Field<int>("parentId") != 0);
var secondLevel = from parent in parents
join child in children
on parent.Field<int>("id") equals child.Field<int>("parentId")
select child;
I think this function might help you figure out the level of tree of each entry so you can use it in your selection:
public int level(DataTable dt, DataRow row)
{
int parentid = int.Parse(row[2].ToString());
if (parentid == 0)
return 1;
else
return 1 + level(dt, GetDataRow(dt,parentid ));
}
public DataRow GetDataRow(DataTable dt, int id)
{
foreach (DataRow r in dt.Rows)
{
if (int.Parse(r[0].ToString()) == id) return r;
}
return null;
}
You have a couple of options to your problem. As proposed by #Ali, you could use recursion like this:
public int level(DataTable dt, DataRow row)
{
int parentid = int.Parse(row[2].ToString());
if (parentid == 0)
return 1;
else
return 1 + level(dt, GetDataRow(dt,parentid ));
}
public DataRow GetDataRow(DataTable dt, int id)
{
foreach (DataRow r in dt.Rows)
{
if (int.Parse(r[0].ToString()) == id) return r;
}
return null;
}
But the problem is that you'll end up iterating though every element and then using recursion on every iteration. If you have absolutely no data relationship between your columns and their level in the tree, besides a parentId, then this is your only solution.
On the other hand, if you do have a relationship, where you have name[level of tree] like Name[A] is tree level 1 and Name[AB] is tree level two with the right node, then iteration through each like:
foreach (DataRow r in dt.Rows)
{
//Pull out the element
//Check the element's level
//Add it to the result set if level <= 2
}
I'd personally prefer to solve the problem by actually building a tree structure or using a SQL WHERE clause, but it's hard to justify the time on it. Depending on where you get this data from, you may also be able to add an additional column which tells you which level the node is in depending on where it's inserted. If it has a grandparent (i.e. two parent nodes) you don't include it in the result set.
DataTable level1 = (from t in dtCategories.AsEnumerable()
where t.Field<int>("parentId") == 0
select t).CopyToDataTable();
DataTable level2 =(from t1 in dtCategories.AsEnumerable()
join t2 in dtCategories.AsEnumerable()
on t1.Field<int>("id") equals t2.Field<int>("parentId")
where t1.Field<int>("parentId") == 0
select t2).CopyToDataTable();
Another way to do it, this will give you a new object which contains the level and the row item itself. This will work for n number of levels...
var nodes = table.AsEnumerable();
//var nodes = new List<TreeNode>();
var parentId = 0;
var countLevel = 0;
var allNods = new List<dynamic>();
while (nodes.Any(p => p.Field<int>("parentId") == parentId))// && countLevel < 2)
// countlevel< 2 only to give you the first 2 levels only...
{
var nodesWithLevel = nodes.Where(p => p.Field<int>("parentId") == parentId)
.Select(p => new { Level = parentId, Node = p });
allNods = allNods.Concat<dynamic>(nodesWithLevel).ToList();
parentId++;
countLevel++;
}
The code currently expects that the root nodes have parentId = 0. Could be changed to null, too of cause...

Querying the id of an entity using linq

In the following method I am trying to fetch the Username by passing the id value where the ids passed as parameter can be multiple values as in csv's (eg: 1,2) and are returned to the calling function as IEnumerable.
Code Follows as below :
[NonAction]
public static IEnumerable<UserProfile> SearchCMSAdmins(string s)
{
//var searchResults = Entities.UserProfiles.Where(item =>item.UserName.Contains(s));
//return searchResults;
string[] ids = s.Split(',');
IEnumerable<UserProfile> results = null;
IList<UserProfile> user = new List<UserProfile>();
for (int i = 0; i < ids.Length; i++)
{
int id = Convert.ToInt32(ids[i].ToString());
var entity = Entities.UserProfiles.Where(item => item.UserId);
//user.Add(entity);
results = results.Concat(entity);
}
return results;
}
Any help is appreciated.
Try using Contains:
var results = Entities.UserProfiles.Where(item => ids.Contains(item.UserId));
http://msdn.microsoft.com/en-us/library/ms132407.aspx
You can get the id array to be of int type, You can either use int.TryParse or Convert.ToInt32 like:
int[] ids = s.Split(',').Select(r=> Convert.ToInt32(r)).ToArray();
Later you can modify your LINQ query as:
IList<UserProfile> user = Entities.UserProfiles
.Where(item=> ids.Contains(item)).ToList();
This would be like Select * from table where ID in (1,2,3) see Creating IN Queries With Linq to SQL for idea
[NonAction]
public static IEnumerable<UserProfile> SearchCMSAdmins(string s)
{
string[] ids = s.Split(',');
foreach (string idAsString in ids)
{
int id = Convert.ToInt32(idAsString);
var entity = Entities.UserProfiles.Where(item => item.UserId == id);
yield return entity;
}
}
should do it (there should be some validation code too in case the id is not an int or the entity is null)

Issue passing a List<int> in C#

I have the following method that calculates the top 20 numbers in a list and returns them.
static public List<int> CalculateTop20(List<int> nums)
{
List<int> Returned = new List<int>();
int count = nums.Count;
for (int j = 0; j < 20; j++)
{
var most = (from i in nums
group i by i into grp
orderby grp.Count() descending
select grp.Key).First();
Returned.Add(most);
nums.RemoveAll(item => item == most);
}
return Returned;
}
Except when I return them to main and try to output them to console they just come up as : System.Collections.Generic.List'1[System.Int32]...
I have multiple other methods passing lists throughout the program but this is the only one that is giving me this issue. Also when I output them right there while they're calculated the numbers are correct.
If you're just calling Console.WriteLine() on the result that's all you'll get, it just calls ToString() on the object which prints the type name.
If you want to output the list you'll need to do something like this:
foreach(var i in list) {
Console.WriteLine(i);
}
If you want the top 20 items from a List why not use LINQ?
// A sample list with 100 integers
var list = new List<int>();
for (var i = 0; i < 100; i++)
{
list.Add(i);
}
// Get the top 20
var top20 = list.OrderByDescending(x => x).Take(20);
Edit:
// Get the top 20 distinct values
var top20 = list.Distinct().OrderByDescending(x => x).Take(20);

How to I make a count on specific category when querystring is empty?

I have a menu on my masterpage / defaultpage where I'm listing x categories.
I would like to make a count of how many products there are in each category.
EX:
Bananas(20)
Apples(8)
Strawberries(5)
So far, I have this:
var listSubMenu = __account.GetAllProductCategories();
var sb = new StringBuilder();
for (int i = 0; i < listSubMenu.Rows.Count; i++)
{
var r = listSubMenu.Rows[i];
var catid = Request.QueryString["thespecific_category_id_but_how_do_i_get_it?"];
var count = __account.GetSpecificCategory(id);
sb.AppendFormat(String.Format(#"<li{0}><a href='/account/products.aspx?categoryid={0}'>{1} ({2})</a></li>", r["cat_id"], r["cat_name"], count.Rows.Count));
}
active_sub_products.Text = sb.ToString();
My DataTable:
public DataTable GetAllProductCategories()
{
const string request =
#"
SELECT * FROM products_category
WHERE cat_active = 1
ORDER BY cat_name ASC
";
using (var query = new MySqlCommand(request))
{
return __dbConnect.GetData(query);
}
}
Obiously i need the specific categoryid, but how to I request that without having querystrings running since it is on the default page.
Am I missing something obious?
Thanks alot.
You should loop through your categories and get the ID from there. Not from the querystring since that is related to you page (as you wrote yourself as well).
Given your example, I would expect that __account.GetAllProductCategories() would return already the ID's you need
In that case you would use something like
var catid = listSubMenu.id;
But id depends on the type of what your __account returns.
If I'm correct in my guess at your result schema from GetAllProductCategories()...
["cat_id"]["cat_name"]
[1][Apples]
[2][Bananas]
[3][Oranges]
var cat_id = r["cat_id"]
or possibly
var cat_id = Int32.Parse(r["cat_id"])
I would also change:
sb.AppendFormat(String.Format(#"<li{0}><a href='/account/products.aspx?categoryid={0}'>{1} ({2})</a></li>", r["cat_id"], r["cat_name"], count.Rows.Count));
To:
sb.AppendFormat(String.Format(#"<li><a href='/account/products.aspx?categoryid={0}'>{1} ({2})</a></li>", cat_id, r["cat_name"], count.Rows.Count));
(There are two changes, (1) <li{0}> to <li> {proper html syntax} and (2) r["cat_id"] to cat_id {you already have it in a variable and string.Format doesn't mind recasting to a string for you})
Beyond that I would suggest looking into an ORM like LinqToSql so you could work directly with objects...
First render the category links in the master page:
** When you call GetAllProductCategories, each row of the result will have at least two columns (cat_id and cat_name).
When you get each row by index (var r = listSubMenu.Rows[i]) the row it returns will have the cat_id and cat_name for that record, I added (var name = r["cat_name"]) for illustration.
If you debug this and step through you should see each iteration through the for loop gives the id variable the next category's id which is then used in the line (var count = __account.GetSpecificCategory(id);)
var listSubMenu = __account.GetAllProductCategories();
var sb = new StringBuilder();
for (int i = 0; i < listSubMenu.Rows.Count; i++)
{
var r = listSubMenu.Rows[i];
var id = Int32.Parse(r["cat_id"]);
var name = r["cat_name"];
var count = __account.GetSpecificCategory(id);
sb.AppendFormat(String.Format(#"<li{0}><a href='/account/products.aspx?categoryid={0}'>{1} ({2})</a></li>", r["cat_id"], r["cat_name"], count.Rows.Count));
}
active_sub_products.Text = sb.ToString();
Then into another textbox or area of the actual page "products.aspx"
var sbProducts = new StringBuilder();
var selectedCat = Request.QueryString["categoryid"];
if(!string.IsNullOrWhitespace(selectedCat))
{
var selectedCatId = Int32.Parse(selectedCat);
var products = __account.GetSpecificCategory(selectedCatId);
for(int j = 0; j < products.Rows.Count; j++)
{
// ... do product listing stuff here
// sbProducts.Append(...);
}
}
else
{
sbProducts.AppendLine("Invalid Category Id Selected!");
}
active_selected_products.Text = sbProducts.ToString();
** Note: when you call Request.QueryString["value"] it will either:
Return null indicating that there isn't a querystring parameter with a matching name
or
Return the string representing the content between value= and the end of the url or the next & found.
** this isn't fully production quality code, there are additional checks you should be doing on the query string value, switch to tryparse for example, check number of products returned and show "No products found for that category" ... etc **

Categories