All, I have an SQL Parser and editor which I intend to integrate in my application. When I run the following query
select * from sys.sysprocesses;
one of the columns returned is a type of byte[]. This column happily gets put into a DataTable, however, when I do
bindingSource.DataSource = result.DataTable;
and attempt to display the data in a DataGridView I get the obvious ArgumentException. In this position, what in the best way to change the byte[] to a string for display in the DataTable?
I could loop through the DataTable and do some thing like
foreach(DataColumn col in dataTable.Columns)
if (col.DataType == typeof(byte[]))
foreach (DataRow row in dataTable.Rows)
row[col] = Encoding.ASCII.GetString((byte[])row[col]);
But this will attempt to put a string into a byte[] column, and will not work. I could clone the DataTable then change the type,
DataTable dtCloned = dataTable.Clone();
dtCloned.Columns[0].DataType = typeof(String);
foreach (DataRow row in dataTable.Rows)
dtCloned.ImportRow(row);
but I need a conversion step to convert the byte[] into a hex string. What is the best and preferably most efficent way to achieve what I want?
Thanks for your time.
This how I did this in the end.
public static void PostProcessData(ref DataTable dataTable)
{
// Convert byte[] columns.
List<DataColumn> colCollRem = new List<DataColumn>();
List<DataColumn> colCollAdd = new List<DataColumn>();
foreach(DataColumn col in dataTable.Columns)
if (col.DataType == typeof(byte[]))
colCollRem.Add(col);
// Remove old add new.
foreach (DataColumn col in colCollRem)
{
int tmpOrd = col.Ordinal;
string colName = String.Format("{0}(Hex)", col.ColumnName);
DataColumn tmpCol = new DataColumn(colName, typeof(String));
dataTable.Columns.Add(tmpCol);
colCollAdd.Add(tmpCol);
foreach (DataRow row in dataTable.Rows)
row[tmpCol] = Utilities.ByteArrayToHexString((byte[])row[col]);
dataTable.Columns.Remove(col);
string colNameNew = colName.Replace("(Hex)", String.Empty);
dataTable.Columns[colName].ColumnName = colNameNew;
dataTable.Columns[colNameNew].SetOrdinal(tmpOrd);
}
}
Using this conversion
public static string ByteArrayToHexString(byte[] p)
{
byte b;
char[] c = new char[p.Length * 2 + 2];
c[0] = '0'; c[1] = 'x';
for (int y = 0, x = 2; y < p.Length; ++y, ++x)
{
b = ((byte)(p[y] >> 4));
c[x] = (char)(b > 9 ? b + 0x37 : b + 0x30);
b = ((byte)(p[y] & 0xF));
c[++x] = (char)(b > 9 ? b + 0x37 : b + 0x30);
}
return new string(c);
}
I hope this helps someone else.
If you are using SQL server 2005 or above you can do the conversion in the query by using the master.sys.fn_varbintohexstr function.
Example:
select
spid,
kpid,
....
master.sys.fn_varbintohexstr(sid)
from
sys.sysprocesses;
Edit
Or you can wrap the DataTable in a class that handles the conversion like this:
(this assumes your grid is not dependent on having a DataTable as datasource)
public class Datasource : IEnumerable
{
private DataTable _dt;
public Datasource(DataTable dt)
{
_dt = dt;
}
public IEnumerator GetEnumerator()
{
foreach (DataRow row in _dt.Rows)
{
IDictionary<string, object> obj = new ExpandoObject();
for (int i = 0; i < _dt.Columns.Count; i++)
{
var value = row[i];
if (value is byte[])
value = BitConverter.ToString((byte[])value);
obj[_dt.Columns[i].ColumnName] = value;
}
yield return obj;
}
}
}
Usage:
bindingSource.DataSource = new Datasource(result.DataTable);
Related
I tried this solution below:
This Row already belongs to another table error when trying to add rows?
I have a Datatable that contains 597 Columns and 20 Rows and are trying to export the data to excel. However, Excel has a maximum column count 256 and so I need to divide the source data into 3 datatables to make the export work.
Below is the code I have written.
var dtmasterdata = data.Tables[name];
for (int j = 1; j < datatableNumberCount; j++)
{
DataTable dt2 = new DataTable();
dt2.TableName = "Master_" + j;
dt2 = dtmasterdata.Copy();
foreach (DataColumn col in dtmasterdata.Columns)
{
DataColumn dtcol = new DataColumn();
dtcol = col;
dt2.Columns.Add(dtcol.ColumnName, dtcol.DataType);
}
for (int k = 0; k < dtmasterdata.Rows.Count; k++)
{
DataRow dr = dt2.NewRow();
dr = dtmasterdata.Rows[k];
dt2.ImportRow(dtmasterdata.Rows[k]);
//dt2.Rows.Add(dr.ItemArray);
}
After that I need to delete few columns like below and I want to create 3 datatables
foreach (DataColumn col in dtmasterdata.Columns)
{
if (j == 1)
{
// condition 1
if (col.Ordinal >= 255)
{
dt2.Columns.RemoveAt(col.Ordinal);
}
}
if (j == 2)
{
// condition 2.
if (col.Ordinal < 255 || col.Ordinal >= 510)
{
dt2.Columns.RemoveAt(col.Ordinal);
}
}
if (j == 3)
{
// condition 3.
if (col.Ordinal <= 510 || col.Ordinal >= 765)
{
dt2.Columns.Add(col);
}
}
}
int worksheetNumber = 1;
string worksheetNameWithNumber = "Master Data";
if (worksheetNumber > 1)
worksheetNameWithNumber = String.Format("{0}_{1}", ws1, worksheetNumber.ToString());
Infragistics.Excel.Worksheet worksheet = wb.Worksheets.Add(worksheetNameWithNumber);
Infragistics.WebUI.UltraWebGrid.UltraWebGrid masterData1 = new Infragistics.WebUI.UltraWebGrid.UltraWebGrid("masterDataGrid");
masterData1.Browser = Infragistics.WebUI.UltraWebGrid.BrowserLevel.UpLevel;
masterData1.DataSource = dt2;
masterData1.DataMember = "Master_" + j;
masterData1.DisplayLayout.HeaderStyleDefault.Font.Bold = true;
masterData1.DisplayLayout.HeaderStyleDefault.Font.Name = "Arial";
masterData1.DisplayLayout.HeaderStyleDefault.Font.Size = FontUnit.Parse("10px");
masterData1.DisplayLayout.HeaderStyleDefault.BackColor = System.Drawing.Color.LightGray;
masterData1.DisplayLayout.RowStyleDefault.Font.Name = "Arial";
masterData1.DisplayLayout.RowStyleDefault.Font.Size = FontUnit.Parse("10px");
Infragistics.WebUI.UltraWebGrid.UltraGridBand masterBand1 = new Infragistics.WebUI.UltraWebGrid.UltraGridBand();
masterData1.Bands.Add(masterBand1);
dgResults.Controls.Add(masterData1);
masterData1.DataBind();
wb.ActiveWorksheet = worksheet;
this.ugWebGridExporter.Export(masterData1, worksheet);
worksheetNumber++;
Your error is because you are trying to add a column to a datatable that already belongs to your source datatable.
dt2.Columns.Add(col);
You can't just iterate through the columns of a datatable and add them to another.
I've a solution to this, which involves cloning the source data and removing what you don't need.
1st, make 3 clones of the datatables you need. Below is an example with me creating my own source table with 596 columns. Notice that clone only takes the data table structure, no data!
var source597ColsTable = new DataTable("Source");
for (var i = 0; i <= 596; i++)
{
source597ColsTable.Columns.Add(new DataColumn("Column" + i , typeof(string)));
}
DataRow newRow = source597ColsTable.NewRow();
source597ColsTable.Rows.Add(newRow);
var cols0To199Table = source597ColsTable.Clone();
var cols200To399Table = source597ColsTable.Clone();
var cols400To596Table = source597ColsTable.Clone();
Next copy all the rows from the source table into the clones. The below is a simple function to do so.
private DataTable CopyRowsFromSource(DataTable sourceTable, DataTable destinationTable)
{
foreach (DataRow row in sourceTable.Rows)
{
destinationTable.Rows.Add(row.ItemArray);
}
return destinationTable;
}
Then call this function for each of your tables.
cols0To199Table = CopyRowsFromSource(source597ColsTable, cols0To199Table);
cols200To399Table = CopyRowsFromSource(source597ColsTable, cols200To399Table);
cols400To596Table = CopyRowsFromSource(source597ColsTable, cols400To596Table);
Finally, remove all the columns from the datatables to give you your split.
private DataTable RemoveColumns(DataTable table, int startCol, int endCol)
{
var colsToRemove = new List<DataColumn>();
for (var colCount = startCol; colCount <= endCol; colCount++)
{
colsToRemove.Add(table.Columns[colCount]);
}
foreach (DataColumn col in colsToRemove)
{
table.Columns.Remove(col);
}
return table;
}
Then call.. again for each cloned table.
cols0To199Table = RemoveColumns(cols0To199Table, 200, 596);
cols200To399Table = RemoveColumns(cols200To399Table, 0, 199);
cols200To399Table = RemoveColumns(cols200To399Table, 200, 396);
cols400To596Table = RemoveColumns(cols400To596Table, 0, 399);
After running this, you will have 3 datatables, columns 0-199, 200-399 and 400-596.
Hope that helps.
I am not sure to have really understood all of your code, but to copy a subset of columns to another datatable there is a very simple method in the DataView class named ToTable where you can list the columns you want in the new table. As added bonus, this method copies also the data in the 20 rows of your original table.
So the only difficult is to list these columns to the method.
You can proceed in this way using linq over the DataColumn collection
string[] firstCols = dtmasterdata.Columns
.Cast<DataColumn>()
.Take(255)
.Select(x => x.ColumnName).ToArray();
string[] secondCols = dtmasterdata.Columns
.Cast<DataColumn>()
.Skip(255)
.Take(255)
.Select(x => x.ColumnName).ToArray();
string[] thirdCols = dtmasterdata.Columns
.Cast<DataColumn>()
.Skip(510)
.Select(x => x.ColumnName).ToArray();
DataTable t1 = dtmasterdata.DefaultView.ToTable("Master_1", false, firstCols);
DataTable t2 = dtmasterdata.DefaultView.ToTable("Master_2", false, secondCols);
DataTable t3 = dtmasterdata.DefaultView.ToTable("Master_3", false, thirdCols);
I'm trying to get some data from DataTable using Linq, but it gives me following error:
Specified cast is not valid.
First of all, i'm using this to paste copied cells from excel to datagridview
private void btnExcel_Click(object sender, EventArgs e)
{
dataGridView1.Columns.Clear();
dataGridView1.Columns.Add("Column1", "Column1");
dataGridView1.Columns.Add("Column2", "Column2");
dataGridView1.Columns.Add("Column3", "Column3");
dataGridView1.Columns.Add("Column4", "Column4");
dataGridView1.Columns.Add("Column5", "Column5");
dataGridView1.Columns.Add("Column6", "Column6");
dataGridView1.Columns.Add("Column7", "Column7");
dataGridView1.Columns.Add("Column8", "Column8");
dataGridView1.Columns.Add("Column9", "Column9");
dataGridView1.Columns.Add("Column10", "Column10");
dataGridView1.Columns.Add("Column11", "Column11");
dataGridView1.Columns.Add("Column12", "Column12");
dataGridView1.Columns.Add("Column13", "Column13");
DataObject o = (DataObject)Clipboard.GetDataObject();
if (o.GetDataPresent(DataFormats.UnicodeText))
{
if (dataGridView1.RowCount > 0)
dataGridView1.Rows.Clear();
string[] pastedRows = Regex.Split(o.GetData(DataFormats.UnicodeText).ToString().TrimEnd("\r\n".ToCharArray()), "\r\n");
int j = 0;
foreach (string pastedRow in pastedRows)
{
string[] pastedRowCells = pastedRow.Split(new char[] { '\t' });
dataGridView1.Rows.Add();
int myRowIndex = dataGridView1.Rows.Count - 1;
using (DataGridViewRow myDataGridViewRow = dataGridView1.Rows[j])
{
for (int i = 0; i < pastedRowCells.Length; i++)
myDataGridViewRow.Cells[i].Value = pastedRowCells[i];
this.dataGridView1.AutoResizeColumns();
}
j++;
}
}
}
Then I'm using this method to convert datagridview to DataTable
private DataTable GetDataTableFromDGV(DataGridView dgv)
{
var dt = new DataTable();
foreach (DataGridViewColumn column in dgv.Columns)
{
if (column.Visible)
{
// You could potentially name the column based on the DGV column name (beware of dupes)
// or assign a type based on the data type of the data bound to this DGV column.
dt.Columns.Add();
}
}
object[] cellValues = new object[dgv.Columns.Count];
foreach (DataGridViewRow row in dgv.Rows)
{
for (int i = 0; i < row.Cells.Count; i++)
{
cellValues[i] = row.Cells[i].Value;
}
dt.Rows.Add(cellValues);
}
return dt;
}
after that using this linq query to get data and display it to datagridview2
DataTable dt = GetDataTableFromDGV(dataGridView1);
//foreach (DataColumn c in dt.Columns)
//{
// MessageBox.Show(c.ColumnName);
//}
var groupedData = from b in dt.AsEnumerable()
group b by b.Field<int>("Column2") into g
select new
{
column2 = g.Key,
column13 = g.Sum(x => x.Field<decimal>("Column13"))
};
foreach (var result in groupedData)
{
dataGridView2.Rows.Add(result);
}
It throws a " Specified cast is not valid."
Basically what I want is shows on picture below:
pic
You never assign a type to any of the DataColumns that you show, so they will have a default data type of string, and calling Field<int> or Field<decimal> will throw an invalid cast exception.
Either assign the appropriate types to the columns when creating the data table or parse the string values:
var groupedData = from b in dt.AsEnumerable()
group b by int.Parse(b.Field<string>("Column2")) into g
select new
{
column2 = g.Key,
column13 = g.Sum(x => decimal.Parse(x.Field<string>("Column13")))
};
I have two datatables:
1.dtEmployee:
|agent_id|agent_name|sum|
2.dtReport:
|sale_date|agent_id|sum |
------------------------
For each record in dtReport I need to find agent_id in dtEmployee and add the value of dtReport["sum"] to dtEmployee["sum"]:
foreach (DataRow r in dtReport)
{
DataRow empRow = dtEmployee.find(dtReport["agent_id"]);
empRow["sum"] += r["sum"];
}
Is there a way that would allow me to accomplish this?
Something like this works:
private void AddValue(string agent_id, decimal sum)
{
DataRow[] row= dtEmployee.Select("agent_id= '"+agent_id+"'");
//since only one record with this agent_id, we take first record of array -> row[0]
decimal dSum= Convert.ToDecimal(row[0][column]);
dSum+= sum;
row[0]["sum"] = dSum;
}
and insert this function into loop:
foreach (DataRow r in dtReport)
{
AddValue(r["agent_id"], r["sum"]);
}
This can be achieved in many ways.
Option 1 :
foreach(DataRow row in dtEmployee.Rows)
{
var update = dtReport.AsEnumerable().FirstOrDefault(r => r.Field<string>("agent_id") == row.Field<string>("agent_id"));
if(update !=null)
row.SetField<float>("sum", update.Field<float>("sum"));
}
Option 2
Another option would be creating new table by joining DataTables
var results = from t1 in dtEmployee.AsEnumerable()
join t2 in dtReport.AsEnumerable()
on t1.Field<int>("agent_id") equals t2.Field<int>("agent_id")
select new { t1, t2 };
// Now we can construct new DataTable
DataTable result = new DataTable() ;
result.Columns.Add("agent_id", typeof(System.Int32));
result.Columns.Add("Name", typeof(System.String));
result.Columns.Add("sum", typeof(float));
foreach(var dr in results )
{
DataRow newRow = results.NewRow();
newRow["agent_id"] = dr.t1.Field<int>("agent_id");
newRow["agent_name"] = dr.t1.Field<string>("agent_name");
newRow["sum"] = dr.t2.Field<float>("sum");
// When all columns have been filled in then add the row to the table
results.Rows.Add(newRow);
}
Working sample
Hope this helps !
You could try something like this. Given that your agent_id and sum are integer.
foreach (DataRow r in dtReport.Rows)
{
dtEmployee.Select(string.Format("agent_id = {0}", r["agent_id"])).ToList<DataRow>().ForEach(
v => { v["sum"] = (v.IsNull("sum") ? 0 : v.Field<int>("sum")) + (r.IsNull("sum") ? 0 : r.Field<int>("sum")); });
}
Or equivalent code
foreach (DataRow r in dtReport.Rows)
{
DataRow[] empRow = dtEmployee.Select("agent_id = " + r["agent_id"]);
for (int i = 0; i < empRow.Length; i++)
{
empRow[i]["sum"] = (empRow[i].IsNull("sum") ? 0 : (int)empRow[i]["sum"]) + (r.IsNull("sum") ? 0 : (int)r["sum"]);
}
}
How to convert a LINQ query result to a DataTable dynamically?
There are solutions where you create another class and specify the column names, but I want the flexibility to change the LINQ structure like column names, column quantities, and have a DataTable generated with the columns names automatically.
Thanks
I've included an extension method that I use with SqlBulkCopy that should do the job, but I'd like to ask why you want to this conversion. There are a very limited number of cases (SqlBulkCopy being one) where a list of objects can't do everything a datatable can. You can use them as binding sources for most controls ... just curious.
public static DataTable toDataTable<T>(this IEnumerable<T> value, List<string> exclusionList)
where T : class
{
var dataTable = new DataTable();
var type = typeof(T);
var properties = type.GetProperties().Where(x => !exclusionList.Contains(x.Name)).ToList();
foreach (var propertyInfo in properties)
{
var propertyType = propertyInfo.PropertyType;
if (!propertyType.IsScalar())
continue;
var nullableType = Nullable.GetUnderlyingType(propertyType);
propertyType = nullableType ?? propertyType;
var dataColumn = new DataColumn(propertyInfo.Name, propertyType);
if (nullableType != null)
dataColumn.AllowDBNull = true;
dataTable.Columns.Add(dataColumn);
}
foreach (var row in value)
{
var dataRow = dataTable.NewRow();
foreach (var property in properties)
{
var safeValue = property.GetValue(row, null) ?? DBNull.Value;
dataRow[property.Name] = safeValue;
}
dataTable.Rows.Add(dataRow);
}
return dataTable;
}
Look into the MoreLinq Nuget package. It has a function ToDataTable()
var LinqResults = from ......;
DataTable dt_Results = LinqResults.ToDataTable();
https://code.google.com/p/morelinq/
It has other VERY useful functions as well:
https://code.google.com/p/morelinq/wiki/OperatorsOverview
They key is to use the LINQ query result as its Implemented IList interface.
If you receive the result as a parameter on a method as an IList object, you can access its columns and rows, this way:
var props = item.GetType().GetProperties();
Refer to this example, it's a small class which please note the it just abstracts the creation of the DataTable, and there is a static method inside called "LINQToDataTable" which you should use.
Step 1, create a class called "GridHelper" (uses System.Data for DataTable structure)
public class GridHelper
{
private DataTable baseDt;
public GridHelper(string tableName)
{
baseDt = new DataTable(tableName);
}
public DataTable getDataTable()
{
return baseDt;
}
public object[,] getObjToFill()
{
object[,] obj = new object[baseDt.Columns.Count, 2];
for (int i = 0; i < baseDt.Columns.Count; i++)
{
obj[i, 0] = baseDt.Columns[i].ColumnName;
}
return obj;
}
public void addColumn(string colName, Type valueType)
{
baseDt.Columns.Add(colName, valueType);
}
public void addRow(object[,] values)
{
DataRow newRow = baseDt.NewRow();
for (int i = 0; i < values.Length / 2; i++)
{
bool colFound = false;
for (int j = 0; j < baseDt.Columns.Count; j++)
{
if (baseDt.Columns[j].ColumnName == values[i, 0].ToString())
{
colFound = true;
break;
}
}
if (colFound == false)
{
throw new Exception("The column " + values[i, 0].ToString() + " has not been added yet.");
}
newRow[values[i, 0].ToString()] = values[i, 1];
}
baseDt.Rows.Add(newRow);
}
public static DataTable LINQToDataTable<T>(T objToList) where T : System.Collections.IList
{
GridHelper ghResult = new GridHelper("Report");
foreach (Object item in objToList)
{
var props = item.GetType().GetProperties();
foreach (var prop in props)
{
ghResult.addColumn(prop.Name, typeof(string));
//prop.Name
//prop.GetValue(item)
}
break;
}
object[,] obj = ghResult.getObjToFill();
foreach (Object item in objToList)
{
var props = item.GetType().GetProperties();
int index = 0;
foreach (var prop in props)
{
//ReportValue(prop.Name, prop.GetValue(item, null));
//prop.Name
obj[index, 1] = prop.GetValue(item);
index++;
}
ghResult.addRow(obj);
}
return ghResult.getDataTable();
}
}
Usage:
var listaReporte =
(from t in dbContext.TablaPruebas
select new
{
Name = t.name,
Score = t.score
}
) .ToList();
DataTable dt = Library.GridHelper.LINQToDataTable(listaReporte);
And that is, use your DataTable as you wish, on a GridView or DataGridView
How can I remove EVERY duplicating row in a DataTable, based on the value of two columns that are in duplication. Unfortunately, I am unable to find the equivalent LINQ Query. (I dont want distinct values even). The table below shall explain my problem
I want to delete every row in duplication based on Column_A and Column_B
COLUMN_A COLUMN_B COLUMN_C COLUMN_D.....
A B
C D
E F
G H
A B
E F
EXPECTED OUTPUT:
COLUMN_A COLUMN_B COLUMN_C COLUMN_D.....
C D
G H
Please help
var rowsToDelete = dataTable.AsEnumerable()
.GroupBy(r => new{A=r["COLUMN_A"],B=r["COLUMN_B"]})
.Where(g => g.Count() > 1)
.SelectMany(g=>g)
.ToList();
foreach (var row in rowsToDelete)
{
dataTable.Rows.Remove(row);
}
You can try with this sample
Link : http://geekswithblogs.net/ajohns/archive/2004/06/24/7191.aspx
Coding
private static void RemoveDuplicates(DataTable tbl,
DataColumn[] keyColumns)
{
int rowNdx = 0;
while(rowNdx < tbl.Rows.Count-1)
{
DataRow[] dups = FindDups(tbl, rowNdx, keyColumns);
if(dups.Length>0)
{
foreach(DataRow dup in dups)
{
tbl.Rows.Remove(dup);
}
}
else
{
rowNdx++;
}
}
}
private static DataRow[] FindDups(DataTable tbl,
int sourceNdx,
DataColumn[] keyColumns)
{
ArrayList retVal = new ArrayList();
DataRow sourceRow = tbl.Rows[sourceNdx];
for(int i=sourceNdx + 1; i<tbl.Rows.Count; i++)
{
DataRow targetRow = tbl.Rows[i];
if(IsDup(sourceRow, targetRow, keyColumns))
{
retVal.Add(targetRow);
}
}
return (DataRow[]) retVal.ToArray(typeof(DataRow));
}
private static bool IsDup(DataRow sourceRow,
DataRow targetRow,
DataColumn[] keyColumns)
{
bool retVal = true;
foreach(DataColumn column in keyColumns)
{
retVal = retVal && sourceRow[column].Equals(targetRow[column]);
if(!retVal) break;
}
return retVal;
}
Test
// create an example datatable with duplicate rows
DataTable tbl = new DataTable();
tbl.Columns.Add("ColumnA");
tbl.Columns.Add("ColumnB");
tbl.Columns.Add("ColumnC");
for(int i = 0; i<10; i++)
{
DataRow nr = tbl.NewRow();
nr["ColumnA"] = "A" + i.ToString();
nr["ColumnB"] = "B" + i.ToString();
nr["ColumnC"] = "C" + i.ToString();
tbl.Rows.Add(nr);
// duplicate
nr = tbl.NewRow();
nr["ColumnA"] = "A" + i.ToString();
nr["ColumnB"] = "B" + i.ToString();
nr["ColumnC"] = "C" + i.ToString();
tbl.Rows.Add(nr);
}
PrintRows(tbl); // show table with duplicates
//Create an array of DataColumns to compare
//If these columns all match we consider the
//rows duplicate.
DataColumn[] keyColumns =
new DataColumn[]{tbl.Columns["ColumnA"],
tbl.Columns["ColumnA"]};
//remove the duplicates
RemoveDuplicates(tbl, keyColumns);