Converting datatable into hierarchical data structure (JSON) using C# - c#

I am executing a SQL query into datatable. The query can return multiple columns. The result is in key value format and represent hierarchical data. See the below screenshot.
The image shows 3 parts. First the data, then the hierarchical representation of data and JSON equivalent.
Currently the image shows 4 level of data, but we can have 6-7 levels of data. The format will remain same but the number of columns can change.
How can i get the desired result using C#? ? I know it is basic programming but I am having hard time with it.

With a fixed-level hierarchy like you are showing in your example, you can use a LINQ query with grouping to generate the tree structure for your JSON. Here is an example with a three-level hierarchy:
static void Main(string[] args)
{
var data = new List<Data>
{
new Data("Food", "1_g", "beverage", "2_b", "hot", "3_h"),
new Data("Food", "1_g", "beverage", "2_b", "cold", "3_c"),
new Data("Food", "1_g", "fruit", "2_f", "juicy", "3_j"),
new Data("Food", "1_g", "fruit", "2_f", "solid", "3_s"),
new Data("Food", "1_g", "cookie", "2_c", "chocolate", "3_cho"),
};
var tree = from l1 in data
group l1 by new { key = l1.Key_L1, name = l1.L1 } into group1
select new
{
key = group1.Key.key,
name = group1.Key.name,
children = from l2 in group1
group l2 by new { key = l2.Key_L2, name = l2.L2 } into group2
select new
{
key = group2.Key.key,
name = group2.Key.name,
children = from l3 in group2
select new { key = l3.Key_L3, name = l3.L3 }
}
};
var serializer = new JavaScriptSerializer();
Console.WriteLine(serializer.Serialize(tree));
Console.ReadLine();
}
class Data
{
public Data(string l1, string k1, string l2, string k2, string l3, string k3)
{
L1 = l1; Key_L1 = k1;
L2 = l2; Key_L2 = k2;
L3 = l3; Key_L3 = k3;
}
public string L1 { get; set; }
public string Key_L1 { get; set; }
public string L2 { get; set; }
public string Key_L2 { get; set; }
public string L3 { get; set; }
public string Key_L3 { get; set; }
}
The above is a working example of the technique using POCOs.
You mention "datatable"; I assume this is referring to the .NET DataTable class? If so, you can use LINQ to query a DataTable. You just need to convert it to an enumerable using the DataSetExtensions. See: LINQ query on a DataTable
Then in the LINQ statement, you replace the list with your data table .AsEnumerable() and replace the property references with .Field<string>(""). Like so:
DataTable dt = // load data table
var tree = from l1 in dt.AsEnumerable()
group l1 by new { key = l1.Field<string>("Key_L1"), name = l1.Field<string>("L1") } into group1
select new
{
// etc.
};
For a variable number of columns, you have to use a recursive approach, like so:
var tree = Descend(dt.AsEnumerable(), 1, 3);
private static System.Collections.IEnumerable Descend(IEnumerable<DataRow> data, int currentLevel, int maxLevel)
{
if (currentLevel > maxLevel)
{
return Enumerable.Empty<object>();
}
return from item in data
group item by new
{
key = item.Field<string>("Key_L" + currentLevel),
name = item.Field<string>("L" + currentLevel)
} into rowGroup
select new
{
key = rowGroup.Key.key,
name = rowGroup.Key.name,
children = Descend(rowGroup, currentLevel + 1, maxLevel)
};
}
One thing to note is that this approach creates an empty children collection on the leaf nodes.

Related

How to join 2 entities to get translated text in Entity Framework 6

I'm using EF 6 Code-First and I want my costumers to introduce new records in multiple languages. To store this I save all the texts in a Dictionary entity with a custom key and reference it from my other entity through this key.
This is a basic sample of my entities:
Dictionary
ID Key IsoCode Value
1 firstEntry en-US My new entry
2 secondEntry en-US My second entry
3 firstEntry es-ES Mi nueva entrada
Entries
ID Name CreationDate
1 firstEntry 2020-11-04
2 secondEntry 2020-11-07
What I want to achieve is to query the Entries entity with an ISO Code, and get a new Entity where the Name field is replaced with Value field from the Dictionary entity.
This is what I have right now:
public List<Entry> GetEntries(string isoCode)
{
var query = (from e in dbContext.Entry
join d in dbContext.Dictionary on e.Name equals d.Key
where d.IsoCode == isoCode
select new
{
entry= e,
Text = d.Value
}).ToList();
return query.Select(t => new Entry
{
Id = t.entry.Id,
Name = t.Text,
CreationDate = t.CreationDate
}).ToList();
}
Is there a better way to do this without creating two lists?
Is this approach of using a Key to get translated text a best practice or am I missing the point here?
Here's a working example of your code using 2 lists instead of your tables (of course).
You can simply create the Entry in the first query. You don't need the .ToList() in that routine. Just return the IEnumerable.
using System;
using System.Collections.Generic;
using System.Linq;
namespace JoinEntities
{
class Program
{
static List<Dict> dictionary = new List<Dict>
{
new Dict
{
ID = 1,
Key = "firstEntry",
IsoCode = "en-US",
Value = "My new entry"
},
new Dict
{
ID = 2,
Key = "secondEntry",
IsoCode = "en-US",
Value = "My second entry"
},
new Dict
{
ID = 3,
Key = "firstEntry",
IsoCode = "es-ES",
Value = "Mi nueva entrada"
},
};
static List<Entry> entries = new List<Entry>
{
new Entry
{
Id = 1,
Name = "firstEntry",
CreationDate = new DateTime(2020, 11, 04)
},
new Entry
{
Id = 1,
Name = "secondEntry",
CreationDate = new DateTime(2020, 11, 07)
}
};
static void Main(string[] args)
{
var list = GetEntries("en-US");
}
public static IEnumerable<Entry> GetEntries(string isoCode)
{
return from e in entries
join d in dictionary on e.Name equals d.Key
where d.IsoCode == isoCode
select new Entry
{
Id = e.Id,
Name = d.Value,
CreationDate = e.CreationDate
};
}
}
internal class Dict
{
public int ID { get; set; }
public string Key { get; set; }
public string IsoCode { get; set; }
public string Value { get; set; }
}
internal class Entry
{
public object Id { get; set; }
public object Name { get; set; }
public object CreationDate { get; set; }
}
}

How to put two different List of tables into one new List c#

I have two lists, one list type is int and the other type is string.
List<int> IntList = new List<int>(){1,2,3,4,5};
List<string> StringList = new List<string>(){"a","b","c","d","e"};
I want to combine these two lists to a new list.
And I create a new class called Table
public class Table
{
public int a { get; set; }
public string b { get; set; }
}
How can i combine IntList and StringList to list<Table>
Like this output
List<Table>
{a=1,b=a}
{a=2,b=b}
{a=3,b=c}
{a=4,b=d}
{a=5,b=e}
Edite
if i want to add the third list
list<int>{0,0,0,0,0}
public class Table
{
public int a { get; set; }
public string b { get; set; }
public string c { get; set; }
}
output
List<Table>
{a=1,b=a,c=0}
{a=2,b=b,c=0}
{a=3,b=c,c=0}
{a=4,b=d,c=0}
{a=5,b=e,c=0}
Edite again
Under the URL of this funtion is vey useful ,it can combine multiple list in to one list
Title :Merge multiple Lists into one List with LINQ
Merge multiple Lists into one List with LINQ
You can use the System.Linq extension method Zip() to return a Table object for each pair of corresponding elements from the two sequences:
List<Table> result = IntList?.Zip(StringList, (i, s) => new Table {a = i, b = s}).ToList();
It looks like you want to use the List Index to join them,
so you can use LINQ Join and List.IndexOf to do it.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<int> IntList = new List<int>() { 1, 2, 3, 4, 5 };
List<string> StringList = new List<string>() { "a", "b", "c", "d", "e" };
var expected = (from t1 in IntList
join t2 in StringList on IntList.IndexOf(t1) equals StringList.IndexOf(t2)
select new Table { a = t1, b = t2 }
).ToList();
Console.WriteLine(expected);
}
}
public class Table
{
public int a { get; set; }
public string b { get; set; }
}
Online Demo Link | C# Online Compiler | .NET Fiddle
The Linq Zip method can do this.
IntList.Zip(StringList, (i, s) => new Table { a = i, b = s });
You can do it with Linq Select()
//If IntList and StringList are of same size
var result = IntList.Select((x, i) => new Table(){a = x, b = StringList[i]});
If length is not equal, then you can add if..else loop,
List<Table> result = new List<Table>();
if(IntList.Count() > StringList.Count())
result = StringList.Select((x, i) => new Table(){a = IntList[i], b = x}).ToList();
else
result = IntList.Select((x, i) => new Table(){a = x, b = StringList[i]}).ToList();
Online Demo: .Net Fiddle

Linq, how to group data by ID and merge data within that ID

From the database I get the following values
PlanningID = GetValue<int>(dataReader["PlanningID"]),
PlanningStatus = GetValue<string>(dataReader["PlanningStatus"]),
Private = GetValue<int>(dataReader["Private"])
Social = GetValue<int>(dataReader["Social"])
PlanningID, PlanningStatus, Private
1, good, 10
1, fair, 5
1, bad, 1
I want to group these by planningID so that it looks like this
public class ClassResult
{
public int planningID { get; set; }
public List<PlanningStatus> PlanningStatus { get; set; }
}
public class PlanningStatus
{
public string PlanningStatus { get; set; }
public int Private { get; set; }
public int Social{ get; set; }
}
I tried this but the output was wrong:
IEnumerable<ClassResult> classResult =
from result in results
group result by new
{
result.PlanningID,
result.PlanningStatus
} into grouping
select new ClassResult
{
PlanningID = grouping.Key.PlanningID,
PlanningStatus = grouping.Key.PlanningStatus,
Private = grouping.First().Private,
Social = grouping.First().Social
};
return lrrResults;
To be honest I got no idea how to do this
Updated projection as advised below but still have multiple planning id's of lets say 1
var results =
from result in results
group result by result.PlanningID
into grouping
select new ClassResult
{
PlanningID = grouping.Key,
PlanningStatusType = grouping.Select(item => new PlanningStatusType
{
PlanningStatus = item.PlanningStatus,
Private = item.Private,
Social = item.Social,
}).ToList(),
LatestChange = grouping.First().LatestChange,
WeeklyChangeType = grouping.First().WeeklyChangeType,
Address = grouping.First().Address
};
In your query above you are grouping by both the PlanningID and PlanningStatus so the groups will contain 1 item each. What you want to do is as following:
Instantiate a new ClassResult as you did but setting the planningID but the .Key (which is now just a single property
Project each item of the grouping to a new object of type PlanningStatus using the .Select()
Code:
var classResult = from result in results
group result by result.PlanningID into grouping
select new ClassResult
{
PlanningID = grouping.Key,
PlanningStatus = grouping.Select(item => new PlanningStatus {
PlanningStatus = item.PlanningStatus,
Private = item.Private,
Social = item.Social
}).ToList()
};
Tested on some sample code and works:

Match two collections dynamically by a criteria

I have a collection of filter criteria objects with each criteria having a name (data object property name) and value property. I have another collection (my data objects) and I need to filter this collection to return data objects which matches the filter criteria.The properties are string values so there is no worry on the type. How can I do this?
Below is the code:
public class FilterCriteria
{
public string ColumnName { get; set; }
public string ColumnValue { get; set; }
}
public class DataObject
{
public string Name { get; set; }
}
public static void Match()
{
var criteria1 = new FilterCriteria() {ColumnName = "Name", ColumnValue = "abc"};
var criteria2 = new FilterCriteria() { ColumnName = "Name", ColumnValue = "xyz" };
var criteriaCollection = new List<FilterCriteria> {criteria1, criteria2};
var data1 = new DataObject() {Name = "xyz"};
var data2 = new DataObject() { Name = "abc" };
var data3 = new DataObject() { Name = "def" };
var dataCollection = new List<DataObject> {data1, data2, data3};
//filter datacollection by the criterias, match any data object with Name property equal to column value
//After the matching I will get the result as data1 & data2.
}
Thanks,
-Mike
NOTE:The FilterCriteria will be serialized to disk using xml serialization.
To get property from its name stored in string you have to use Reflection, for example:
DataObject someDataObject = ...;
typeof(DataObject).GetProperty("SomePropertyName").GetValue(someDataObject)
Then, combining it with LINQ:
var filtered = dataCollection.Where(obj =>
criteriaCollection.Any(cond => obj.GetType()
.GetProperty(cond.ColumnName)
.GetValue(obj)
.Equals(cond.ColumnValue)))
.ToList();

Combining multiple linq queries and concatenated results into a smaller query

Can I restructure the following into a more compact linq query, ideally without the introduction of a helper function?
var revPerUnitChanges =
from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
let field = "Rev/Unit"
select new {
Field = field,
From = row.Field<decimal>(field, DataRowVersion.Original),
To = row.Field<decimal>(field, DataRowVersion.Current),
ItemIds = row.Field<string>("ItemIds"),};
var costPerUnitChanges =
from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
let field = "Cost/Unit"
select new {
Field = field,
From = row.Field<decimal>(field, DataRowVersion.Original),
To = row.Field<decimal>(field, DataRowVersion.Current),
ItemIds = row.Field<string>("ItemIds"), };
var numUnitsChanges =
from row in this.DataTable.GetChanges(DataRowState.Modified).AsEnumerable()
let field = "Units"
select new {
Field = field,
From = row.Field<decimal>(field, DataRowVersion.Original),
To = row.Field<decimal>(field, DataRowVersion.Current),
ItemIds = row.Field<string>("ItemIds"), };
var changes =
revPerUnitChanges
.Concat(costPerUnitChanges
.Concat(numUnitsChanges))
.Where(c => c.From != c.To);
Start out by creating a helper class to hold onto the data. (Your code doesn't have any problems using anonymous types, but if you want to refactor sections into methods it'll be much easier with a named class.)
public class MyClass //TODO give better name
{
public MyClass(DataRow row, string field) //You could have a public static generate method if it doesn't make sense for this to be a constructor.
{
Field = field;
From = row.Field<decimal>(field, DataRowVersion.Original);
To = row.Field<decimal>(field, DataRowVersion.Current);
ItemIds = row.Field<string>("ItemIds");
}
public string Field { get; set; }
public decimal From { get; set; }
public decimal To { get; set; }
public string ItemIds { get; set; }
}
Now that we have that out of the way the query is fairly straightforward.
var changes = dataTable.GetChanges(DataRowState.Modified)
.AsEnumerable()
.Select(row => new[]{ //create 3 new items for each row
new MyClass(row, "Rev/Unit"),
new MyClass(row, "Cost/Unit"),
new MyClass(row, "Units"),
})
.SelectMany(item => item) //flatten the inner array
.Where(item => item.From != item.To);

Categories

Resources