C# DataTable fields value structuring - c#

I have a Datatable in C# with 2 fields and the first fields has the Label and it's value as well in next rows. And the second field has the values only.
Field1 Field2
10th Nick
Nick John
11th Marry
John Andy
Marry Sandy
12th null
Andy null
Sandy null
Is there any way or logic I can apply to structure my data to upload in SQL table in format like below.
Class Name
10th Nick
11th John
11th Marry
12th Andy
12th Sandy
This data is just an example but I need to apply the logic in around million of rows with thousands of class label and looking for something which can pick the field1 value one by one and then search that value in field2
If found - Add that in Name field
if not found - Add that in class field
var list = new List<TempFieldList>();
foreach (DataRow row in ListTable.Rows)
{
list.Add(new TempFieldList{
field1 = row["Field1"].ToString(),
field2 = row["Field2"].ToString()
});
}
foreach (DataRow row in ListTable.Rows)
{
var result = list.FirstOrDefault(x => x.field2 == row["Field1"].ToString());
//row.Dump();
if(result != null )
ResultTable.Rows.Add("");
else
ResultTable.Rows.Add(row["Field1"].ToString(), null);
}
Any better way, or code? As what I was trying is not working unfortunately.

Use the below pseudocode and you'll have a list of custom objects that have class & Names matched up. Then you can loop through that inserting into the database.
List<string> classes = new List();
classes.add("10th");
class.add("11th"); //and so on
string currentClass = new String();
SomeObjectWithClassAndName obj = new SomeObjectWithClassAndName();
List<SomeObjectWithClassAndName> myList = new List();
foreach(DataRow row in yourDataTable)
{
if(classes.Contains(row.Field<string>[1])
{
currentClass = row.Field<string>[1];
}
else{
obj = new SomeObject..;
obj.Class = currentClass;
obj.Name = row.Field<string>[1];
MyList.Add(obj);
}
}

Related

How to get data as IEnumerable<MODEL> from DataTable by using LINQ?

I have a DataTable with some attributes data get from a database, each data in that DataTable belong to a product, and each product could have more than one attribute.
So i have another DataTable which has all the products in a foreach by looping through each row i'm adding each product to it's List<Plu> like this:
var productAttr = new List<Plu.Attributi>();
foreach (DataRow rowPlu in dt.Rows)
{
try
{
int id = (int)rowPlu["ID_PLUREP"];
plu.Add(new Plu(
id,
(string)rowPlu["CODICE_PRP"],
(string)rowPlu[ESTESA],
(string)rowPlu[DESCR], (float)rowPlu["PRE_PRP"],
rowPlu.IsNull("IMG_IMG") ? null : (string)rowPlu["IMG_IMG"],
productAttr,
null,
(int)rowPlu["ID_MENU_PRP"]
));
}
catch
{
return plu;
}
}
For now the productAttr is empty but now i need to add to each product it's attributes, so with the following function i get a DataTable filled with data from database with all product attributes:
var attributi = Attributi(connection, idNegozio);
and then i was trying to do something like this inside the foreach
foreach (DataRow rowPlu in dt.Rows)
{
try
{
int id = (int)rowPlu["ID_PLUREP"];
plu.Add(new Plu(
id,
(string)rowPlu["CODICE_PRP"],
(string)rowPlu[ESTESA],
(string)rowPlu[DESCR], (float)rowPlu["PRE_PRP"],
rowPlu.IsNull("IMG_IMG") ? null : (string)rowPlu["IMG_IMG"],
from row in attributi.AsEnumerable() where row.Field<int>("ID_PLUREP_VAT") == id select row,
null,
(int)rowPlu["ID_MENU_PRP"]
));
}
catch
{
return plu;
}
}
But the LINQ returns a EnumerableRowCollection while i need a IEnumerable<Plu.Attribute>, so i was wondering if there is a lazy way to cast the .AsEnumerable to IEnumerable<Plu.Attrbute>...
The problem is, that the DataTable only knows which values are in the cells. It does not know what these values stand for. It does not know that the number in column 0 is in fact an Id. It doesn't know that the string in column 1 is the Name of a Customer, and the DateTime in column 2 is the Birthday of the Customer.
If you will be using the contents of this Datatable (or similar DataTables) for other queries in the future, you need some translation from DataRow to the items that they stand for.
Once you've got the translation from DataRow to Plu, you can convert your DataTable to an IEnumerable<Plu>, and do other LINQ processing on it.
Usage will be like:
DataTable table = ...
var mySelectedData = table.AsEnumerable().ToPlus()
.Where(plu => ...)
.Select(plu => new {...})
.ToList();
You need two extension methods: one that converts a DataRow to a Plu and one that converts a sequence of DataRows to a sequence of Plus. See extension methods demystified
public static Plu ToPlu(this DataRow row)
{
// TODO implement
}
public static IEnumerable<Plu> ToPlus(this IEnumerable<DataRow> dataRows)
{
// TODO: exception if null dataRows
return dataRows.Select(row => row.ToPlu());
}
If desired, create an extension method from DataTable to extract the Plus:
public static IEnumerable<Plu> ExtractPlus(this DataTable table)
{
// TODO: exception if table null
return table.AsEnumerable().ToPlus();
}
Usage:
DataTable table = ...
IEnumerable<Plu> plus = table.ExtractPlus();
I haven't got the faintest idea what a Plu is, and you forgot to mention the relevant properties of the Plu, so I'll give you an example of a table that contains Customers:
class Customer
{
public int Id {get; set;} // Id will be in column 0
public string Name {get; set;} // Name will be in column 1
...
}
public static Customer ToCustomer(this DataRow row)
{
return new Customer
{
Id = (int)row[0],
Name = (string)row[1],
};
}
If desired, instead of columnIndex you can use the name of the column.
So by only creating a ToPlu, and a one-liner method to convert sequences of DataRows to a sequence of Plus, you've extended LINQ with your methods to read your tables.
To be on the safe side, consider creating an extension method that converts a sequence of Plus to a DataTable. This way, the layout of the table is in one location: ToPlu(DataRow) and ToDataRow(Plu). Future changes in the table layout will be easier to manage, users of your DataTable will only think in sequences of Plus.
You can do something like below. If you want IEnumerable<Plu> you can remove the .ToList() from the end.
dt.AsEnumerable().Select(x => new Plu {
Id = x.Field<int>("ID_PLUREP"),
CodicePrep = x.Field<string>("CODICE_PRP"),
....
Attributes = attributi.AsEnumerable()
.Where(y => y.Field<int>("ID_PLUREP_VAT") == x.Field<int>("ID_PLUREP"))
.Select(z => new Attributi
{
....
}).ToList(),
....
}).ToList();

How to get the columns index from datatable which column name contains like 'ColumnName'

I have a datatable which contains columns created dynamically:
Name | Role | Comm w1 | Role | Comm w2 | .... | Total Comm |
I want to get all columns index which contains Comm.
The expected result should be:
2
4
...
You can use LINQ to do this fairly easily.
First we need to cast the Columns to DataColumn because LINQ needs it to be IEnumerable<DataColumn> but Columns is DataColumnCollection. Then constrict the columns to ones that contain "Comm", then select the ordinal of each column, then make that a list.
List<int> columnIndexes = dt.Columns.Cast<DataColumn>()
.Where(column => column.ColumnName.Contains("Comm"))
.Select(column => column.Ordinal).ToList();
Your question is unclear on what you want the output to be, whether you just want to print the positions of the columns to screen, or store them in a way to use them later.
Furthermore, this is a very simple solution.. if it wasn't for that then I wouldn't be participating.. so for future reference please provide what you have tried and what you are expecting. It helps all that are involved in trying to help you find a solution. Please reference "How to ask a good question" if you experience any trouble.
using System;
using System.Data;
using System.Xml;
public class Program
{
public static void Main()
{
var dt = new DataTable { Columns = { "Name", "Role", "Comm w1", "Comm w2", "Total Comm"}};
foreach(DataColumn column in dt.Columns){
if(column.ColumnName.Contains("Comm")){
Console.WriteLine(column.Ordinal.ToString());
}
}
}
}
// Output:
// 2
// 3
// 4
Let me know if this helps.
The below should do what you need
foreach (DataColumn col in dataTable.Columns)
{
if (col.ColumnName.Contains("Comm"))
{
var index = col.Ordinal;
}
}
But as mentioned, it is best if you attempt something first.
The other answers are right, it will get you what you are asking for, but I don't think how they use them is quite right. I would do something like this:
List<int> indexes = new List<int>();
foreach (DataColumn column in dataTable.Columns)
{
if (column.ColumnName.Contains("Comm"))
{
indexes.Add(column.Ordinal);
}
}
This way you have the list of indexes afterwards to use however you wish.

How to return dynamic object from SQL query

I have situation where a storeprocdure return collection, but I do not how the object structure because the query is very dynamic.
One query can return:
Id | Location | MarketSegment | ... n columns
and another can return
Id | Sales Rep | Location | Region | ... n columns
I am simply just return a "object" as you can see in the code below. I know this won't work, but how can I set it up so it does?
using (DbContext db = new Context())
{
var items = db.Database.SqlQuery<object>(
"SP #Param1, #Param2",
new SqlParameter("Param1", ped),
new SqlParameter("Param2", 25)
).ToList();
return Request.CreateResponse<List<object>>(HttpStatusCode.OK, items);
}
EDIT:
I don't know if showing the SP will help in anyways, except if I can explain it more.
Each columns are represented as Custom Fields. Users are able to create n numbers of Custom Fields. So If you run the SP for User1 and he has 5 custom fields, then each custom fields will be represented in Columns, but If User2 has 3 custom fields, only 3 columns will be represented. What I don't have control over is the Custom Field Name and number of custom fields.
If on SQL 2016 or newer, add "FOR JSON AUTO" to your query to return as JSON, e.g:
var json = db.Database.SqlQuery<string>("Select x, y, z FROM tbl FOR JSON AUTO").First();
Then use Json.Net to create a dynamic object using
var myDynamic = JObject.Parse(json)
You can't use SqlQuery<T> for custom fields.
Creates a raw SQL query that will return elements of the given generic
type. The type can be any type that has properties that match the
names of the columns returned from the query, or can be a simple
primitive type. - MSDN
But, you can use ExecuteReader to achieve that.
using (var db = new Context())
{
db.Database.Connection.Open();
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = "SP #Param1, #Param2";
cmd.Parameters.Add(new SqlParameter("Param1", ped));
cmd.Parameters.Add(new SqlParameter("Param2", 25));
List<List<object>> items = new List<List<object>>();
var reader = cmd.ExecuteReader();
while (reader.Read())
{
var item = new List<Object>();
items.Add(item);
for (int i = 0; i < reader.FieldCount ; i++)
item.Add(reader[i]);
}
return Request.CreateResponse<List<object>>(HttpStatusCode.OK, items);
}
If you know what all the possible columns could be returned, there is no issue with using a class that has more properties than you need.
public class Data
{
public int ID {get;set;}
public string SalesRep {get;set;}//Simply will be empty in the first example, but populated in the second.
public string Location {get;set;}
}

Moving the column of RenderTable

I have a renderTable and I am adding rows and columns to the table as follows-
RenderTable renderTable = new RenderTable();
DataTable dt = GetData();
foreach (DataRow row in dt.Rows)
{
var header = renderTable.Rows[renderTable.Rows.Count];
header[0].Text = "Column 1";
header[1].Text = "Column 2";
header[2].Text = "Column 3";
header[1].Text = "Column 4";
var data = renderTable.Rows[renderTable.Rows.Count];
data [0].Text = row["col1"].ToString(); // 10
data [1].Text = row["col2"].ToString(); // 11
data [2].Text = row["col3"].ToString(); // 12
data [3].Text = row["col4"].ToString(); // 13
}
This is working fine and table is rendering as folllows-
Column 1 Column2 Column3 Column4
10 11 12 13
My requirement is, now I want to move the column 4 to another place like 2nd place as follows . (this place can differ depending on condition)
Column 1 Column4 Column2 Column3
10 13 11 12
I tried Insert method but it is not working for me as the insert index may change.
Is there any function of render table to move the column to specified index.
Please suggest any alternative if any.
We regret to mention but there isn't any function that can allow the moving of column of RenderTable to a specified index since the Cols of C1PrintDocument is ReadOnly.
I've done this by creating a new class from System.Web.UI.WebControls.GridView. I override CreateColumns which is used to return an array of the column objects in order. I read a cookie from the page (this allows me to change the columns via a cookie on the page) and create a new column array based on the cookie. This cookie is just a string of the column names in the order required with a | separator. I had another column picker page that would set this cookie. If you don't need to change the columns with a cookie this is not needed -- you could read / create this string from a database or configuration file. I believe the code is well commented and clear -- one note, our application has a requirement to include hidden columns, so I add those to the end of the column list before I return the array.
using System.Collections;
using System.Linq;
using System.Web.UI.WebControls;
public class ChangeColumnGridView : System.Web.UI.WebControls.GridView
{
protected override ICollection CreateColumns(PagedDataSource dataSource, bool useDataSource)
{
// Get the needful from the base class
var baseColList = base.CreateColumns(dataSource, useDataSource);
var inColList = baseColList.OfType<object>();
// Get our column order
string columnOrder;
if (Page.Request.Cookies["colOrder"] != null)
columnOrder = Page.Request.Cookies["colOrder"].Value;
else
return baseColList;
// change it to an array
string[] columnOrderA = columnOrder.Split(new char[] { '|' });
// this is where we will put our results
ArrayList newColumnList = new ArrayList();
// look for each name in the list and add when we find it.
foreach (string name in columnOrderA)
{
var found = inColList.Where((c) => c.ToString() == name).FirstOrDefault();
if (found != null)
newColumnList.Add(found);
}
// look for non-visible items in the list and add them if we don't already have them.
foreach (var a in inColList)
{
if (((System.Web.UI.WebControls.DataControlField)a).Visible == false)
{
var found = newColumnList.Cast<object>().Where((c) => c.ToString() == a.ToString()).FirstOrDefault();
if (found == null)
newColumnList.Add(a);
}
}
return newColumnList;
}
}

DataTable.Merge Issue: Adds New Column (Due to Case Sensitivity)

I am facing a strange issue with DataTable.Merge in development. I am trying to merge two tables into one. I am using the following code
gridData.Merge(existingGridData);
gridData has columns "PK", "A", "B", "AA", "BB" and existingGridData contains "PK", "A", "B", "C".
The merge result actually contains the following columns "PK", "A", "B", "AA", "BB", "A", "B", "C" (The columns are duplicated).
Any Idea? I also tried with
gridData.Merge(existingGridData, false, MissingSchemaAction.Ignore);
In this case, the primary key is not copied to the merge result and I am getting Constraint Violation exception.
My Table actually has extended properties (for Columns) and I also tried after copying the extended properties, so that two tables have same extended properties. I verified the Column Name, caption, DataType and all are same.
Two tables are constructed in different locations and have no previous relations.
I tried various scenarios in a new solution and it works fine.
Any help? Thanks in Advance.
See below Code it is giving the correct output
DataTable dataTable = new DataTable();
dataTable.Columns.Add("PK");
dataTable.Columns.Add("A");
dataTable.Columns.Add("B");
dataTable.Columns.Add("AA");
dataTable.Columns.Add("BB");
DataRow drRow = dataTable.NewRow();
drRow[0] = 1;
drRow[1] = 2;
drRow[2] = 1;
drRow[3] = 2;
drRow[4] = 1;
dataTable.Rows.Add(drRow);
drRow = dataTable.NewRow();
drRow[0] = 3;
drRow[1] = 4;
drRow[2] = 12;
drRow[3] = 23;
drRow[4] = 14;
dataTable.Rows.Add(drRow);
DataTable newTable = new DataTable();
newTable.Columns.Add("PK");
newTable.Columns.Add("A");
newTable.Columns.Add("B");
newTable.Columns.Add("C");
newTable.ExtendedProperties.Add("TimeStamp",DateTime.Now);
drRow = newTable.NewRow();
drRow[0] = 5;
drRow[1] =6;
drRow[2] = 5;
drRow[3] = 6;
newTable.Rows.Add(drRow);
drRow = newTable.NewRow();
drRow[0] = 7;
drRow[1] = 8;
drRow[2] = 55;
drRow[3] = 66;
newTable.Rows.Add(drRow);
dataTable.Merge(newTable,false);
You have 2 methods which you can follow. Either you create the logic yourself, like what I am doing in the following method, or you can use inbuilt LINQ functions, like my second approach.
///
/// This method is used to merge a set of data tables, based on common columns between them both
///
///
///
///
public static DataTable MergeDataTables(DataTable dt1, DataTable dt2)
{
try
{
// Get common columns
var commonColumns = dt1.Columns.OfType().Intersect(dt2.Columns.OfType(), new DataColumnComparer());
// Create the result which is going to be sent to the user
DataTable result = new DataTable();
// Add all the columns from both tables
result.Columns.AddRange(
dt1.Columns.OfType()
.Union(dt2.Columns.OfType(), new DataColumnComparer())
.Select(c => new DataColumn(c.Caption, c.DataType, c.Expression, c.ColumnMapping))
.ToArray());
// Add the records of each data table to the new data table, based on the columns
var rowData = dt1.AsEnumerable().Join(
dt2.AsEnumerable(),
row => commonColumns.Select(col => row[col.Caption]).ToArray(),
row => commonColumns.Select(col => row[col.Caption]).ToArray(),
(row1, row2) =>
{
var row = result.NewRow();
row.ItemArray = result.Columns.OfType().Select(col => row1.Table.Columns.Contains(col.Caption) ? row1[col.Caption] : row2[col.Caption]).ToArray();
return row;
},
new ObjectArrayComparer());
// Loop and add
foreach (var row in rowData)
result.Rows.Add(row);
// Return result...
return result;
}
catch (Exception ex)
{
throw new Exception("Problem while merging data tables. Check that there are common columns between the 2 data tables. Error : " + ex.Message);
}
}
Or do the following;
// Results
DataTable reportResult1 = new DataTable(); // Your data table 1
DataTable reportResult2 = new DataTable(); // Your data table 2
// Merge tables
var commonColumns = reportResult1.Columns.OfType().Intersect(reportResult2.Columns.OfType(), new DataColumnComparer());
// Remove DB Nulls, replace with empty strings
reportResult1.RemoveColumnNulls(commonColumns.ToList());
reportResult2.RemoveColumnNulls(commonColumns.ToList());
reportResult1.PrimaryKey = commonColumns.ToArray();
result.Merge(reportResult2, false, MissingSchemaAction.AddWithKey);
result.Merge(reportResult1, false, MissingSchemaAction.AddWithKey);
return result;
Let me know if you manage. I would go for option number 2, as it is much more optimised in terms of performance. What is done is the common columns between 2 tables are extracted, another table is created, and you will have a FULL OUTER JOIN effect. The common columns would be used as the JOINed columns.
Root Cause: I found why it happened. I have changed the caption of the gridData to lowercase some where in code. In detail, I fetched data from database. At that time the column name was in ALLCAPS, say "SAMPLEFIELD". Then in the following code, I renamed the column name (not caption) to "SampleField". The existingGridData also contained "SampleField". I expected both fields to be merged but its not.
When debugging through the .Net Framework Code, I found that there is a dictionary with Column Names (Columns.columnFromName), when you add Columns for the first time. But when you change the column name, this dictionary is not updated - if you provide the same column name with CAPS changed (but.. will be updated if its a different name).
This dictionary is used during Merge. Since the cases are changed it fails. Try the following code.
for (int i = 0; i < existingData.Columns.Count; i++)
{
DataColumn src = existingData.Columns[i];
DataColumn dest = (gridData.Columns.Contains(src.ColumnName)) ? gridData.Columns[src.ColumnName] : null;
if (dest == null)
{
//You will get the column here
}
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
MethodInfo minfo = typeof(DataColumnCollection).GetMethod("Contains", bindingFlags); //This override is used internally duting Merge
var result = minfo.Invoke(gridData.Columns, new object[]{src.ColumnName, true});
dest = ((bool)result) ? gridData.Columns[src.ColumnName] : null;
if (dest == null)
{
//You wont get the column here. Its case sensitive
}
}
Some other issues related to case sensitivity.
http://forums.asp.net/t/707552.aspx
Hope this helps some one
I faced the same issue. I changed all column names to upper case but still the issue was there. I took the default view of both tables and it fixed the issue. Anyway this will remove all the table expression. eg: column expressions.
So the steps was
Make sure all the columns are in same case.
Use default view of both table.
Additionally make sure all the columns has same max lengths. It may cause problems if exceeds the length.
dataTable1 = dataTable1.DefaultView.ToTable();
dataTable2 = dataTable2.DefaultView.ToTable();
dataTable1 .Merge(dataTable2 , true, MissingSchemaAction.Add);

Categories

Resources