Displaying self-referencing table's data in a c# application - c#

I have following table:
---------------------
Id Title Parent
---------------------
1 Parent NULL
2 Level_1 1
3 Level_2 1
4 Level_3 1
5 Level NULL
6 Level_New 5
Now I want to display these data in my console application, I know I need a recursive function but no idea how to do it becuase I want to read these data using ADO.NET not EntityFramework.In EF I could define a model that has a navigation property for children:
public class Menu
{
public int Id { get; set; }
public string Title { get; set; }
public int? Parent { get; set; }
public ICollection<Menu> Children { get; set; }
}
But the problem is that I don't want to use EF. I want to do it using raw ADO.NET

Recursion isn't fun, this is a solution that I used to test for a much larger recursion
public class MyObject
{
public string Id;
public string ParentId;
public string Name;
public string Comments;
}
a lot of this code you wont need, but this should give you want you need on recursion.
private void BindTree(IEnumerable<MyObject> list, TreeNode parentNode, string previousNode)
{
var myObjects = list as IList<MyObject> ?? list.ToList();
var nodes = myObjects.Where(x => (parentNode == null ? x.ParentId == "[].[].[(root)]" : x.ParentId == parentNode.Value));
var listOfNodeNames = new List<string>();
foreach (var node in nodes)
{
var newNode = new TreeNode(node.Name, node.Id);
BindTree(myObjects, newNode, previousNode);
}
}
The above code does the recursion I need ( code you wont need stripped out ) and builds a treeview on a page based on data from a datatable.
But, this should give you want you need to do your recursion.

You need to pull data from server first, then construct tree on client side. Beware of circular reference.
First, change your Menu class to ensure that Children will never null
public class Menu
{
public Menu()
{
Children = new HashSet<Menu>();
}
public int Id { get; set; }
public string Title { get; set; }
public int? Parent { get; set; }
public ICollection<Menu> Children { get; private set; }
}
Then pull the data from database, and construct the tree
var connBuilder = new SqlConnectionStringBuilder();
connBuilder.DataSource = "localhost";
connBuilder.InitialCatalog = "YourDatabaseName";
connBuilder.IntegratedSecurity = true;
using (var con = new SqlConnection(connBuilder.ToString()))
{
con.Open();
var list = new List<Menu>();
//pull data from database
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "SELECT Id, Title, Parent FROM [dbo].[YourTableName]";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
list.Add(new Menu
{
Id = reader.GetInt32(0),
Title = reader.GetString(1),
Parent = reader.IsDBNull(2) ?(int?) null : reader.GetInt32(2)
});
}
}
}
//construct tree
var newList = new List<Menu>();
foreach (var l1 in list)
{
if (l1.Parent == null)
{
newList.Add(l1);
}
foreach (var l2 in list)
{
if (l2.Parent == l1.Id)
{
l1.Children.Add(l2);
}
}
}
// do whatever you want with newList
}
You will get data like this

Related

Removing an item from list and re-arranging them

I have a list which contains order property of string.
This is what the data looks like:
1
2
2.1
2.1.1
2.1.2
2.2
2.2.1
2.2.2
3
3.1
3.2
3.3
4
And the structure of class is like this
public class CMNavBarDefaultDto
{
public int ID { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public string? Comments { get; set; }
public string Order { get; set; }
public bool ToShowGray { get; set; }
public bool IsNew { get; set; }
}
if the user delete any order from the list, like user delete 3.1 then 3.2 and 3.3 rearrange
3.2 become 3.1, 3.3 become 3.1,
if the user delete any parent like 1 then all the hierarchy should be maintained in a new form
like 2 become 1 and its child and sub-child should start from 1.
Can anyone suggest to me what approach is helpful in this scenario?
Seems to me, best method is to use list of uints to specify level as needed. You could then parallel the html table with Order object to update table as needed by having delete operation actually modify Order, which then updates html table.
The question is what to do if user deletes middle level? For example, if user deletes 1.1 in the following order, what happens to 1.1.1 and 1.1.2?
1
1.1
1.1.1
1.1.2
1.2
Does it become like below?
1
1.1
1.2
1.3 (was 1.2)
Knowing these rules, you can create a conversion function to create parallel Order and then operate on it as needed. The below code assumes all prior levels are defined as you go deeper (i.e., if you have a level 2, it is assumed the prior level was 2 or 1). At any point you can jump up levels (i.e., you could be at level 4 and then have the next level 1). If you don't follow these rules, the ToString function would throw Exception.
public class Order
{
var list = new List<unit>();
// needed: static function or constructor that takes html table string and returns Order and methods to modify Order
public override string ToString()
{
var sb = new StringBuilder();
var stack = Stack<(uint, string))>();
uint current = 0;
string order = "";
foreach (var next in list)
{
if (next > stack.Count)
{
// next is in deeper level, make sure prior level(s) exist.
if ((current == 0) || (next != (stack.Count + 1))) throw new Exception("missing level");
// prior level(s) exist, push current level in stack in case lower level needed later, then restart count within next level
stack.Push((current, order));
order = $"{order}{current}.";
current = 0;
}
else if (next < stack.Count)
{
// at lower level! pop out levels from stack until we get back to next level
while (stack.Count > next)
{
(current, order) = stack.Pop();
}
}
// append next level to output
current++;
sb.AppendLine($"{order}{current}");
}
return sb.ToString();
}
}
The following will create the tree. You have to add code to do add and remove items in tree. When you add or remove you have to renumber the items
using System;
using System.Linq;
using System.Text;
using System.Collections;
using System.Collections.Generic;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
List<CMNavBarDefaultDto> list = new List<CMNavBarDefaultDto>()
{
new CMNavBarDefaultDto() { Order = "1"},
new CMNavBarDefaultDto() { Order = "2"},
new CMNavBarDefaultDto() { Order = "2.1"},
new CMNavBarDefaultDto() { Order = "2.1.1"},
new CMNavBarDefaultDto() { Order = "2.1.2"},
new CMNavBarDefaultDto() { Order = "2.2"},
new CMNavBarDefaultDto() { Order = "2.2.1"},
new CMNavBarDefaultDto() { Order = "2.2.2"},
new CMNavBarDefaultDto() { Order = "3"},
new CMNavBarDefaultDto() { Order = "3.1"},
new CMNavBarDefaultDto() { Order = "3.2"},
new CMNavBarDefaultDto() { Order = "3.3"},
new CMNavBarDefaultDto() { Order = "4"},
};
Tree root = new Tree();
root.MakeTree(list, root);
}
}
public class CMNavBarDefaultDto
{
public int ID { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public string? Comments { get; set; }
public string Order { get; set; }
public bool ToShowGray { get; set; }
public bool IsNew { get; set; }
}
public class Tree
{
public CMNavBarDefaultDto cmNav { get; set; }
public int[] orderInt { get; set; }
public List<Tree> children { get; set; }
public void MakeTree(List<CMNavBarDefaultDto> cmNavs, Tree parent)
{
List<Tree> list = new List<Tree>();
foreach(CMNavBarDefaultDto cmNav in cmNavs)
{
Tree node = new Tree() { cmNav = cmNav, orderInt = cmNav.Order.Split(new char[] { '.' }).Select(x => int.Parse(x)).ToArray() };
list.Add(node);
}
int level = 0;
list = list.OrderBy(x => x.orderInt.Length).ToList();
MakeTreeRecursive(list, parent, level);
}
public void MakeTreeRecursive(List<Tree> list, Tree parent, int level)
{
var groups = list.GroupBy(x => x.orderInt[level]).ToList();
foreach(var group in groups)
{
if (parent.children == null) parent.children = new List<Tree>();
parent.children.Add(group.First());
if (group.Count() > 1)
{
if (group.Last().orderInt.Length == level + 1)
{
group.First().children = new List<Tree>();
group.First().children = group.Skip(1).ToList();
}
else
{
MakeTreeRecursive(group.Skip(1).ToList(), group.First(), level += 1);
}
}
}
}
}
}

using linq and c# to create a nested list object from a flat list object

I have a question. It's about linq in combination with c#.
I want to create a tree structure from a flatten structure in a pre defined object structure.
The following code which I've got work, but both are not exactly what i want.
In linq:
var result = listAgenderingen.GroupBy(records => records.Agnnummer)
.Select(group => new { AgnNummer = group.Key, Items = group.ToList()}).ToList();
the issue is that this does not result in the object I want.
So I've rewritten this to the following code
List<string> test = listAgenderingen.Select(x => x.Agnnummer).Distinct().ToList();
foreach (var item in test)
{
List<Agendering> listAgendering = listAgenderingen.Where(agend => agend.Agnnummer == item).OrderBy(ord => ord.Agnnummer).ToList();
AgnAgendering AgnAgendering = new AgnAgendering() {AgnNummer =item, Agenderingen = listAgendering };
}
this code actually works correct. but for 200000 records, it's taking a lot of time while the original linq takes a few seconds.
my question is can the linq be rewritten so it will create or convert to the richt object?
the structure of the classes:
public class Agendering
{
public int AgnID { get; set; }
public string Agnnummer { get; set; }
}
public class AgnAgendering
{
public string AgnNummer { get; set; }
public List<Agendering> Agenderingen { get; set; }
}
I hope someone has a sollution.
If I understand correctly, you want:
var result = listAgenderingen.GroupBy(records => records.Agnnummer)
.Select(group => new AgnAgendering { AgnNummer = group.Key, Agenderingen = group.ToList()}).ToList();
Your properties naming makes it absolutely unreadable and unclear.
Assuming that you have a flat structure like:
public class Item
{
public int ID { get; set; }
public int? ParentID { get; set; }
}
and you want a tree-like structure:
public class TreeItem
{
public int ID { get; set; }
public TreeItem Parent { get; set; }
public List<TreeItem> Children { get; set; }
public TreeItem(int id)
{
ID = id;
Children = new List<TreeItem>();
}
public TreeItem(int id, TreeItem parent) : this(id)
{
Parent = parent;
}
}
You can do most optimally in O(n) using Dictionary:
Item[] items = ...;
Dictionary<int, TreeItem> result = new Dictionary<int, TreeItem>();
foreach (var item in items.OrderBy(x => x.ParentID ?? -1))
{
TreeItem current;
if (item.ParentID.HasValue)
{
TreeItem parent = result[item.ParentID]; // guaranteed to exist due to order
current = new TreeItem(item.ID, parent);
parent.Children.Add(current);
} else {
current = new TreeItem(item.ID);
}
}
TreeItem[] treeItems = result.Values.ToArray();

Populate Model class from Data in Backend

I have a database that has two tables as follows, Please ignore the data but the format looks as follows
Now I have a Model class that is constructed as follows
public class FamilyModel
{
public string Name { get; set; }
public List<FamilyModel> FamilyList { get; set; }
public FamilyModel()
{
FamilyList = new List<FamilyModel>();
}
}
Now all I want is to get data from the two tables and populate the list.
So I have a stored procedure that returns data as follows
So I have written some code to populate the above class. But it dosent work. I get a count of 5 when I debug. I want the count to be 2 and when expanded I want something like FamilyA ->{Nick, Tom, Pam}.. FamilyB->{Harry} and so on. Please help fixing this code.
public static FamilyModel familyData()
{
//FamilyModel fml = new FamilyModel();
//fml.FamilyList = new List<FamilyModel>();
using (SqlConnection con = new SqlConnection(#"Data Source=(LocalDB)\v11.0; AttachDbFilename=|DataDirectory|\Families.mdf; Integrated Security=True; Connect Timeout=30;"))
{
con.Open();
SqlCommand cmd = new SqlCommand("sp_GetFamilies", con);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read()) {
FamilyModel fm = new FamilyModel();
fm.Name = dr["FamilyName"].ToString();
foreach (var item in dr["ChildName"].ToString())
{
if (Convert.ToInt32(dr["id"]) == Convert.ToInt32(dr["FID"]))
{
fm.FamilyList.Add(new FamilyModel() { Name = dr["ChildName"].ToString() });
}
}
}
return fm;
}
}
Here is some source code that should get the right idea across. Below it, I've included some explanation for what's going on.
using Dapper;
public class FamilyModel
{
public int Id { get; set;}
public string FamilyName { get; set; }
public List<Person> Members { get; set; } = new List<Person>();//Initializer for Auto-Property, C#6<= only
}
public class Person
{
public int Id { get; set;}
public string Name { get; set; }
}
public class DatabasePOCO
{
public string FamilyName { get; set; }
public string ChildName { get; set; }
public int Fid { get; set; }
public int Id { get; set;}
}
void Main()
{
using (IDbConnection conn = new SqlConnection("..."))
{
conn.Open();
var raw = conn.Query<DatabasePOCO>("sp_GetFamilies",
commandType: CommandType.StoredProcedure);//Could be dynamic, not typed
var familyList = raw
.GroupBy(x => x.Fid)
.Select(x =>
{
var rawMembers = x.ToList();
var fId = x.First().Fid;
var fName = x.First().FamilyName;
var members = rawMembers.Select(y => new Person
{
Id = y.Id,
Name = y.ChildName
});
return new FamilyModel
{
Id = fId,
FamilyName = fName,
Members = members.ToList()
};
});
//Whatever else you want to do here
}
}
Consider using Dappper. It is a great ORM that makes accessing data from database really easy. It's designed to work with SQL Server, but I've had success using Oracle too, and most other RMDBS systems will probably work.
Consider using Slapper. If you have control over your stored procedure, this can reduce a lot of the boilerplate code below.
If you use Dapper (I hope you do), you can play around with C# dynamic objects, or you can create a POCO to help get some type enforcement on your code.
Understand if you care about reference equality. The code I provided below does not enforce reference equality of objects. Reference equality, in my experience, doesn't buy you much and is a pain to enforce.
You need to distinguish between a new row in the data set and a new FamilyModel. One way to do this is to declare a list of models, then look up the "right" one before you add the current child row:
var rootModel = new FamilyModel();
rootModel.Name = "root";
// ... Set up data reader ...
while (dr.Read())
{
//This requires support for the Id in your FamilyModel:
var id = (int)dr["Id"];
//You could also use ".Single(...)" here
var fm = rootModel.FamilyList.Where(x => x.Id == id).First();
if (fm == null)
{
fm = new FamilyModel();
fm.Name = dr["FamilyName"].ToString();
rootModel.FamilyList.Add(fm);
}
fm.FamilyList.Add(new FamilyModel() { Name = dr["ChildName"].ToString() });
}
For each row in your database query, you'll:
Try to look up that family in your list
If you don't find one, create a new one. Add it to your top-level list.
Add the child name as a sub-element of the "current" family.

Insert new document in nested documents in MongoDB

I am a beginner in MongoDB. Please see my models below.
public class Technology
{
public Technology()
{
ProductGroups = new List<ProductGroup>();
}
[BsonRepresentation(BsonType.ObjectId)]
public ObjectId _id { get; set; }
public string Name { get; set; }
public IEnumerable<ProductGroup> ProductGroups { get; set; }
}
public class ProductGroup
{
[BsonRepresentation(BsonType.ObjectId)]
public ObjectId _id { get; set; }
public string Name { get; set; }
}
Now the data shows like below.
I am try to add the ProductGroup ( it's a BsonDocument Collection) collection in Technology.
Use generic types where you can. Because this code parent["ProductGroups"] is dangerous place for any refactoring.
Your task can be done in one query
var productGroup = new ProductGroup { Id = ObjectId.GenerateNewId(), Name = model.Name };
var collection = database.GetCollection<Technology>("Technology");
var update = Builders<Technology>.Update.AddToSet(x => x.ProductGroups, productGroup);
await collection.FindOneAndUpdateAsync(x => x.Id == model._id, update);
Change your Model of Technology as
[BsonElementAttribute("productgroups")]
public IList<ProductGroup> ProductGroups{ get; set; }
Then,
var productGroup = new BsonDocument().Add("_id", productGroup_id).Add("Name", name);
var technologies = database.GetCollection("technology");
var technology = technologies.FindOneById(ObjectId.Parse(technology_id));
technology["productgroups"] = new BsonArray().Add(BsonValue.Create(productGroup));
technologies.Save(technology);
#CodingDefined I change my code as per the v2.0.1.27
Please see my code below. Thank you very much for your help.
var productGroup = new BsonDocument()
.Add("_id", ObjectId.GenerateNewId())
.Add("Name", model.Name);
BsonDocument parent = null;
var _parent = Collection.FindOneByIdAs(typeof(BsonDocument), model._id);
if (_parent != null)
{
parent = _parent.ToBsonDocument();
parent["ProductGroups"] = new BsonArray().Add(BsonValue.Create(productGroup));
Collection.Save(parent);
}
Please make sure, the new child record is not clearing the existing records
parent["ProductGroups"] = parent["ProductGroups"].AsBsonArray.Add(productGroup);

I want to use an asp Treeview control with LinqToSql

I am trying to understand how to implement a treeview control - it all looks hideously complicated. However, the Treeview control would be more appropriate.
I have an SQL table containing fields ID and ParentLevelID.
I have added a basic Treeview control to my code:
<asp:TreeView ID="tvLevels" runat="server">
</asp:TreeView>
I want to populate this table using LinqToSQL. Presently, I am displaying the same data as a Gridview:
protected void SetupLevelsPanel()
{
// display levels according to current parentId
_svsCentralDataContext = new SVSCentralDataContext();
object levels;
if (_intParentLevelId == 0)
{
levels = (from sl in _svsCentralDataContext.SVSSurvey_Levels
where sl.ParentLevelID == null && sl.SurveyID == _intSurveyId
select new
{
sl.ID,
sl.SurveyID,
sl.UserCode,
sl.ExternalRef,
sl.Description,
sl.ParentLevelID,
sl.LevelSequence,
sl.Active
});
backUpButton.Visible = false;
}
else
{
levels = (from sl in _svsCentralDataContext.SVSSurvey_Levels
where sl.ParentLevelID == _intParentLevelId && sl.SurveyID == _intSurveyId
select new
{
sl.ID,
sl.SurveyID,
sl.UserCode,
sl.ExternalRef,
sl.Description,
sl.ParentLevelID,
sl.LevelSequence,
sl.Active
});
}
grdLevels.DataSource = levels;
grdLevels.DataBind();
GrdLevelButtons();
}
How can I convert this information to use my Treeview control?
This is my solution.
On my code behind page:
private void BuildTree()
{
tvLevels .Nodes.Clear();
_svsCentralDataContext = new SVSCentralDataContext();
List<DataAccessLayer.Level> items = DataAccessLayer.Levels.GetLevels(_intSurveyId).ToList();
List<DataAccessLayer.Level> rootItems = items.FindAll(p => p.ParentLevelId == null);
foreach (DataAccessLayer.Level item in rootItems)
{
var tvi = new TreeNode(item.Description, item.Id.ToString(CultureInfo.InvariantCulture) );
BuildChildNodes(tvi, items, item.Id);
tvLevels.Nodes.Add(tvi);
}
}
private void BuildChildNodes(TreeNode parentNode, List<DataAccessLayer.Level> items, int parentId)
{
List<DataAccessLayer.Level> children = items.FindAll(p => p.ParentLevelId == parentId).ToList();
foreach (DataAccessLayer.Level item in children)
{
var tvi = new TreeNode(item.Description, item.Id.ToString(CultureInfo.InvariantCulture));
parentNode.ChildNodes.Add(tvi);
BuildChildNodes(tvi, items, item.Id);
}
}
Class Levels.cs
using System;
using System.Collections.Generic;
using System.Linq;
using SVSVoidSurveyDesigner.Database;
namespace SVSVoidSurveyDesigner.DataAccessLayer
{
public class Levels
{
public static IEnumerable<Level> GetLevels(int intSurveyId)
{
var dataContext = new SVSCentralDataContext();
var levels = (from l in dataContext.SVSSurvey_Levels where l.SurveyID == intSurveyId
select new Level
{
Id = l.ID,
SurveyId = l.SurveyID,
UserCode = l.UserCode ,
ExternalRef = l.ExternalRef ,
Description = l.Description ,
ParentLevelId = (l.ParentLevelID),
LevelSequence = ( l.LevelSequence ),
Active = Convert .ToBoolean( l.Active )
});
return levels;
}
}
}
Class Level.cs
namespace SVSVoidSurveyDesigner.DataAccessLayer
{
public class Level
{
public int Id { get; set; }
public int SurveyId { get; set; }
public string UserCode { get; set; }
public string ExternalRef { get; set; }
public string Description { get; set; }
public int? ParentLevelId { get; set; }
public int? LevelSequence { get; set; }
public bool Active { get; set; }
}
}

Categories

Resources