Specify Which Columns Are In A Datatable - c#

I have a stored procedure that returns extra columns. I don't have control of the stored procedure. I would like to use the following form to generate my worksheet:
ws.Cells.LoadFromDataTable(dt, true, OfficeOpenXml.Table.TableStyles.Light8)
How can I output just the columns I want?
I tried to figure out a Linq query but the problem is that the column names are inaccessible so I can't specify which columns I would like.
SqlConnection cx = new SqlConnection(util.GetConnectionString());
SqlCommand cmd = new SqlCommand("StoredRept", cx);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter ta = new SqlDataAdapter();
ta.SelectCommand = cmd;
DataTable dt = new DataTable();
ta.Fill(dt);
FileInfo newFile = new FileInfo("c:\temp");
ExcelPackage epp = new ExcelPackage(newFile);
var ws = epp.Workbook.Worksheets.Add("WS");
// here is where I would like to copy certain columns off into another data table but I haven't been able to figure out how
ws.Cells.LoadFromDataTable(dt, true, OfficeOpenXml.Table.TableStyles.Light8);
Any help would be greatly appreciated.

If removing some columns from the table is the only problem, that is easy to resolve. Try something like this:
var dt = new DataTable();
dt.Columns.Add("First", typeof(string));
dt.Columns.Add("Second", typeof(string));
dt.Columns.Add("Third", typeof(string));
dt.Columns.Add("Fourth", typeof(string));
dt.Columns.Add("Fifth", typeof(string));
for (var i = 1; i < 6; i++)
{
dt.Rows.Add($"First {i}", $"Second {i}", $"Third {i}",$"Fourth {i}",$"Fifth {i}");
}
//dt.Dump();//If you have linqpad this is handy to dump table to output
//REMOVE THE COLUMNS.
dt.Columns.RemoveAt(1);
dt.Columns.RemoveAt(2);
//dt.Dump();//again in linqpad this dumps table with remaining 3 columns
You can use following method to find column by name and remove it:
var col=dt.Columns["Second"];
dt.Columns.Remove(col);
Here is linq query to get list with desired columns.
var lq = (from DataRow r in dt.Rows
select new { First = r[0], Second=r[1], Third=r["Fifth"]}
).ToList();
lq.Dump();
Note how you can use both column index or name to get value from row object.

Can you load your spreadsheet with IEnumerable<T> instead of DataTable? If so:
var someColumns = dt
.AsEnumerable()
.Select(row => new
{
When = row.Field<DateTime>("When"),
What = row.Field<string>("What"),
HowMany = row.Field<int>("ColumnNameInDatabase"),
});
If you need DataTable, you can convert IEnumerable to it via any method, like this or this.
BUT
Frankly, I answered your problem, but IMO you've a greater problem at hand, which is using those antiquated DataTable objects instead of IEnumerable<T>. If you consider, for example, Dapper library, you could do this:
util.GetConnectionString()
.Query("StoredRept", commandType: CommandType.StoredProcedure) // Extension method from Dapper
.Select(dyn => new
{
When = (DateTime)dyn.When,
What = (string)dyn.What,
HowMany = (int)dyn.ColumnNameInDatabase
})
I see you're using EPPlus library for Excel manipulation. It can load IEnumerable<T> data, you are not restricted to DataTable.

Related

Adding a column from one Datatable to another datatable of a dataset [duplicate]

How can I copy 1 data column from 1 data table to a new datatable. When I try to do it, I get the error Column 'XXX' already belongs to another DataTable.?
dataColumn = datatable1.Columns[1];
datatable2 = new DataTable();
datatable2.Columns.Add(dataColumn);
Thanks in Advance
You cannot copy DataColumns. What you'll need to do is create a new DataColumn in the new datatable with the same data type as in the old datatable's column, and then you need to run a FOR loop to bring in all the data from the old datatable to the new datatable.
See the following code. This assumes that the datatables have exactly the same number of rows.
DataTable dt1 = new DataTable();
DataTable dt2 = new DataTable();
dt2.Columns.Add("ColumnA", dt1.Columns["ColumnA"].DataType);
for (int i = 0; i < dt1.Rows.Count; i++)
{
dt2.Rows[i]["ColumnA"] = dt1.Rows[i]["ColumnA"];
}
Also, If the data you are copying are reference types and not value types you might want to see if a .Clone() method is available for the type, or make one yourself. Just doing 'this = that' in the FOR loop will not work on reference types.
You cannot copy a DataColumn. (DataColumns are very tightly coupled with their tables)
Instead, you can add a new column with the same name and datatype.
You might be looking for DataTable.Clone(), which will create a structual copy of an entire table. (With the same schema, but no data)
Just a thought, are your DataTables both in the same DataSet?
If so, you can create a named DataRelation between the columns of two tables (think foreign key).
Then you can add a Calculated DataColumn to your table that has its Expression property set to "Child(RelationName).ColumnName" or "Parent(RelationName).ColumnName" depending on the direction of the relationship.
This will give you the same effect as copying the column, but I believe it only evaluates it lazily. So maybe it will give you what you need.
There is an example here of how this works. The example uses the Sum aggregate function, but you just need to reference the column name and it will duplicate it in your DataTable
myDataSet.Relations.Add(
"Orders2OrderLines",
myDataSet.Tables["Orders"].Columns["OrderID"],
myDataSet.Tables["OrderLines"].Columns["OrderID"]);
ordersTable.Columns.Add("OrderTotal", typeof(decimal), "Sum(Child(Orders2OrderLines).ExtendedPrice)");
HTH
The problem is caused by the c# can not reuse the object instance created and uses it on multiples DataTables. For this it is necessary to create a new object DataCollumn for each loop iteration.
foreach (DataTable table in DATASET.Tables)
{
DataColumn yourDataCollumn = new DataColumn("Name of DataCollumn", typeof(Your data type));
// your logic here
}
Hope it's help...
I used the below to merge two tables using mostly LINQ and only looping through the rows at the end. I wouldn't call it pretty but it does work. Using the join to prevent some of the assumptions listed above.
DataTable tableOne = getTableOne();
DataTable tableTwo = getTableTwo();
var oneColumns = tableOne.Columns.Cast<DataColumn>()
.Select(p => new Column(p.ColumnName, DataType))
.ToArray();
var twoColumns = tableTwo.Columns.Cast<DataColumn>()
.Select(p => new DataColumn(p.ColumnName, p.DataType))
.ToArray();
var matches = (from a in tableOne.AsEnumerable()
join b in tableTwo.AsEnumerable() on a["column_name"] equals b["column_name"]
select a.ItemArray.Concat(b.ItemArray)).ToArray();
DataTable merged = new DataTable();
merged.Columns.AddRange(oneColumns);
merged.Columns.AddRange(twoColumns);
foreach (var m in matches) { merged.Rows.Add(m.ToArray()); }
No looping required , Refer this , Hope this should solve your problem...
DataTable dt = new DataTable();
//fill the dt here
DataTable dt2 = new DataTable();
string[] strCols = {"Column Name to copy"};
dt2 = dt.DefaultView.ToTable("newTableName", false, strCols);

Linq on DataTable: select specific column into datatable, not whole table

I'm running a LINQ query on a datatable in c#.
I want to select specific columns rather than the whole row and enter the result into a datatable. How can i do that??
My Code:
public DataTable getConversions(string c_to, string p_to)
{
var query = from r in matrix.AsEnumerable()
where r.Field<string>("c_to") == c_to &&
r.Field<string>("p_to") == p_to
select r;
DataTable conversions = query.CopyToDataTable();
If you already know beforehand how many columns your new DataTable would have, you can do something like this:
DataTable matrix = ... // get matrix values from db
DataTable newDataTable = new DataTable();
newDataTable.Columns.Add("c_to", typeof(string));
newDataTable.Columns.Add("p_to", typeof(string));
var query = from r in matrix.AsEnumerable()
where r.Field<string>("c_to") == "foo" &&
r.Field<string>("p_to") == "bar"
let objectArray = new object[]
{
r.Field<string>("c_to"), r.Field<string>("p_to")
}
select objectArray;
foreach (var array in query)
{
newDataTable.Rows.Add(array);
}
Try Access DataTable easiest way which can help you for getting perfect idea for accessing DataTable, DataSet using Linq...
Consider following example, suppose we have DataTable like below.
DataTable ObjDt = new DataTable("List");
ObjDt.Columns.Add("WorkName", typeof(string));
ObjDt.Columns.Add("Price", typeof(decimal));
ObjDt.Columns.Add("Area", typeof(string));
ObjDt.Columns.Add("Quantity",typeof(int));
ObjDt.Columns.Add("Breath",typeof(decimal));
ObjDt.Columns.Add("Length",typeof(decimal));
Here above is the code for DatTable, here we assume that there are some data are available in this DataTable, and we have to bind Grid view of particular by processing some data as shown below.
Area | Quantity | Breath | Length | Price = Quantity * breath *Length
Than we have to fire following query which will give us exact result as we want.
var data = ObjDt.AsEnumerable().Select
(r => new
{
Area = r.Field<string>("Area"),
Que = r.Field<int>("Quantity"),
Breath = r.Field<decimal>("Breath"),
Length = r.Field<decimal>("Length"),
totLen = r.Field<int>("Quantity") * (r.Field<decimal>("Breath") * r.Field<decimal>("Length"))
}).ToList();
We just have to assign this data variable as Data Source.
By using this simple Linq query we can get all our accepts, and also we can perform all other LINQ queries with this…
Here I get only three specific columns from mainDataTable and use the filter
DataTable checkedParams = mainDataTable.Select("checked = true").CopyToDataTable()
.DefaultView.ToTable(false, "lagerID", "reservePeriod", "discount");
LINQ is very effective and easy to use on Lists rather than DataTable. I can see the above answers have a loop(for, foreach), which I will not prefer.
So the best thing to select a perticular column from a DataTable is just use a DataView to filter the column and use it as you want.
Find it here how to do this.
DataView dtView = new DataView(dtYourDataTable);
DataTable dtTableWithOneColumn= dtView .ToTable(true, "ColumnA");
Now the DataTable dtTableWithOneColumn contains only one column(ColumnA).
Your select statement is returning a sequence of anonymous type , not a sequence of DataRows. CopyToDataTable() is only available on IEnumerable<T> where T is or derives from DataRow. You can select r the row object to call CopyToDataTable on it.
var query = from r in matrix.AsEnumerable()
where r.Field<string>("c_to") == c_to &&
r.Field<string>("p_to") == p_to
select r;
DataTable conversions = query.CopyToDataTable();
You can also implement CopyToDataTable Where the Generic Type T Is Not a DataRow.

How to query a DataTable in memory to fill another data table

I am trying to update a Microsoft report. What it does is write out how many clients where excluded from a conversion process and for what reason. Currently the program writes all of the deleted clients back to the server then queries it back to fill a specialty table with the results.
Here is the current query:
SELECT DeletedClients.Reason,
COUNT(DeletedClients.Reason) AS Number,
CAST(CAST(COUNT(DeletedClients.Reason) AS float)
/ CAST(t.Total AS float)
* 100 AS numeric(4, 1)) AS percentage
FROM DeletedClients CROSS JOIN
(SELECT COUNT(*) AS Total
FROM DeletedClients AS DeletedClients_1
WHERE (ClinicID = #ClinicID)) AS t
WHERE (DeletedClients.ClinicID = #ClinicID)
AND (DeletedClients.TotalsIdent = #ident)
GROUP BY DeletedClients.Reason, t.Total
ORDER BY Number DESC
What I would like to do is not write DeletedClients to the server as it already exists in memory in my program as a DataTable and it is just slowing down the report and filling the database with information we do not need to save.
My main question is this, Either :
How do I query a data table to make a new in memory data table that has the same results as if I wrote out the the SQL server and read it back in with the query above?
OR
How in Microsoft Reports do you do a group by clause for items in a Tablix to turn =Fields!Reason.Value =Fields!Number.Value =Fields!percentage.Value into something similar to the returned result from the query above?
You can use DataTable.Select to query the DataTable.
DataTable table = GetDataTableResults();
DataTable results = table.Select("SomeIntColumn > 0").CopyToDataTable();
Or for more complex queries, you can use LINQ to query the DataTable:
DataTable dt = GetDataTableResults();
var results = from row in dt.AsEnumerable()
group row by new { SomeIDColumn = row.Field<int>("SomeIDColumn") } into rowgroup
select new
{
SomeID = rowgroup.Key.SomeIDColumn,
SomeTotal = rowgroup.Sum(r => r.Field<decimal>("SomeDecimalColumn"))
};
DataTable queryResults = new DataTable();
foreach (var result in query)
queryResults.Rows.Add(new object[] { result.SomeID, result.SomeTotal });
There are two ways that I can think of to query the data table. Below is an example using both ways.
using System;
using System.Data;
namespace WindowsFormsApplication1
{
static class Program
{
[STAThread]
static void Main()
{
var deletedClients = GetDataTable();
// Using linq to create the new DataTable.
var example1 = deletedClients.AsEnumerable()
.Where(x => x.Field<int>("ClinicId") == 1)
.CopyToDataTable();
// Using the DefaultView RowFilter to create a new DataTable.
deletedClients.DefaultView.RowFilter = "ClinicId = 1";
var rowFilterExample = deletedClients.DefaultView.ToTable();
}
static DataTable GetDataTable()
{
var dataTable = new DataTable();
// Assumes ClinicId is an int...
dataTable.Columns.Add("ClinicId", typeof(int));
dataTable.Columns.Add("Reason");
dataTable.Columns.Add("Number", typeof(int));
dataTable.Columns.Add("Percentage", typeof(float));
for (int counter = 0; counter < 10; counter++)
{
dataTable.Rows.Add(counter, "Reason" + counter, counter, counter);
}
return dataTable;
}
}
}

LINQ Query to DataTable.DataSource

I am trying to perform a LINQ query on a DataTable and show the result in another DataTable. My source DataTable looks something like this:
DataTable myDataTable = new DataTable();
myDataTable.Columns.Add("OrderID", typeof(int));
myDataTable.Columns.Add("Date", typeof(DateTime));
myDataTable.Columns.Add("UnitsPurchased", typeof(int));
The resulting DataTable looks like this when filled:
Order ID Date Units Purchased
16548 10/15/09 250
17984 11/03/09 512
20349 01/11/10 213
34872 01/15/10 175
My current LINQ query looks like this:
IEnumerable<DataRow> query = (from row in myDataTable.AsEnumerable()
where row.UnitsPurchased > 200
select new
{
row.OrderID,
row.Date,
row.UnitsPurchased
}) as IEnumerable<DataRow>;
resultDataTable.DataSource = query.CopyToDataTable<DataRow>();
Every time I run this code query is null. I can see that that the as IEnumerable<DataRow> is the culprit, but it makes no since to me since DataTable.AsEnumerable() returns an IEnumerable<DataRow>. Any help would be appreciated.
When you select new { }, you're actually getting an IEnumerable<(Anonymous Type)>, not IEnumerable<DataRow>. So your as IEnumerable<DataRow> will return null, since it can't be directly cast.
Either select new MyDataRow(constructor using values...) or something, or just do var query =... without the as cast. There's an msdn article about using CopyToDataTable with a non-DataRow generic parameter, though I haven't read it in depth, but selecting new DataRows is probably the easier solution.
Why do you have to create a new Anonymous Type. When you can simply do this .
DataTable myDataTable = new DataTable();
myDataTable.Columns.Add("OrderID", typeof(int));
myDataTable.Columns.Add("Date", typeof(DateTime));
myDataTable.Columns.Add("UnitsPurchased", typeof(int));
var datarow1 = myDataTable.NewRow();
datarow1.SetField("OrderID", 16548);
datarow1.SetField("Date", DateTime.Parse("10/10/09"));
datarow1.SetField("UnitsPurchased", 250);
var datarow2 = myDataTable.NewRow();
datarow2.SetField("OrderID", 17984);
datarow2.SetField("Date", DateTime.Parse("11/03/09"));
datarow2.SetField("UnitsPurchased", 512);
var datarow3 = myDataTable.NewRow();
datarow3.SetField("OrderID", 20349);
datarow3.SetField("Date", DateTime.Parse("01/11/10"));
datarow3.SetField("UnitsPurchased", 213);
var datarow4 = myDataTable.NewRow();
datarow4.SetField("OrderID", 34872);
datarow4.SetField("Date", DateTime.Parse("10/01/10"));
datarow4.SetField("UnitsPurchased", 175);
myDataTable.Rows.Add(datarow1);
myDataTable.Rows.Add(datarow2);
myDataTable.Rows.Add(datarow3);
myDataTable.Rows.Add(datarow4);
var filteredTable = myDataTable.AsEnumerable().OfType<DataRow>().Where(row => row.Field<int>("UnitsPurchased") > 200).Select(r => r);
resultDataTable.DataSource = filteredTable.CopyToDataTable();

Merge 2 DataTables and store in a new one

If I have 2 DataTables (dtOne and dtTwo) and I want to merge them and put them in another DataTable (dtAll). How can I do this in C#? I tried the Merge statement on the datatable, but this returns void. Does Merge preserve the data? For example, if I do:
dtOne.Merge(dtTwo);
Does dtOne change or does dtTwo change and if either one changes, do the changes preserve?
I know I can't do this because Merge returns void, but I want to be able to store the Merger of both dtOne and dtTwo in dtAll:
//Will Not work, How do I do this
dtAll = dtOne.Merge(dtTwo);
The Merge method takes the values from the second table and merges them in with the first table, so the first will now hold the values from both.
If you want to preserve both of the original tables, you could copy the original first, then merge:
dtAll = dtOne.Copy();
dtAll.Merge(dtTwo);
Instead of dtAll = dtOne.Copy(); in Jeromy Irvine's answer you can start with an empty DataTable and merge one-by-one iteratively:
dtAll = new DataTable();
...
dtAll.Merge(dtOne);
dtAll.Merge(dtTwo);
dtAll.Merge(dtThree);
...
and so on.
This technique is useful in a loop where you want to iteratively merge data tables:
DataTable dtAllItems = new DataTable();
foreach(var item in items)
{
DataTable dtItem = getDataTable(item); // some function that returns a data table
dtAllItems.Merge(dtItem);
}
dtAll = dtOne.Copy();
dtAll.Merge(dtTwo,true);
The parameter TRUE preserve the changes.
For more details refer to MSDN.
DataTable dtAll = new DataTable();
DataTable dt= new DataTable();
foreach (int id in lst)
{
dt.Merge(GetDataTableByID(id)); // Get Data Methode return DataTable
}
dtAll = dt;
This is what i did for merging two datatables and bind the final result to the gridview
DataTable dtTemp=new DataTable();
for (int k = 0; k < GridView2.Rows.Count; k++)
{
string roomno = GridView2.Rows[k].Cells[1].Text;
DataTable dtx = GetRoomDetails(chk, roomno, out msg);
if (dtx.Rows.Count > 0)
{
dtTemp.Merge(dtx);
dtTemp.AcceptChanges();
}
}

Categories