I have a datatable which I have filterd using Linq:
result = myDataTable.AsEnumerable().Where(row => row.Field<string>("id").Contains(values));
I have also tried using CopyToDataTable methods like
result.CopyToDataTable()
and
result.CopyToDataTable<DataRow>()
but they didn't work.
How can I convert result to new DataTable?
I have search many Stack Overflow questions and many other tutorials but I can't find what I want.
UPDATE
I have concatenated HashSet to comma separated values, I think I should use array of String or HashSet?
I suggest you create object of DataTable and import row in it by calling ImportRow() function , that will resolve issue.
DataTable.ImportRow Method
Example code.
DataTable tblClone = datTab.Clone();
foreach (DataRow datRow in datTab.Rows)
{
tblClone.ImportRow(datRow);
}
for you it will be like
var result = myDataTable.AsEnumerable().Where(row => row.Field<string>("id").Contains(values));
DataTable tblClone = myDataTable.Clone();
foreach(DataRow dr in result)
tblClone.ImportRow(dr);
.CopyToDataTable<DataRow>() returns a DataTable, it will not modify the variable unless you re-assign it.
result = myDataTable.AsEnumerable().Where(row => row.Field<string>("id").Contains(values));
Then you actually need a DataTable object.
DataTable resultDT = result.CopyToDataTable<DataRow>();
Edit: As Tim pointed out, if no rows are returned by your query, an exception will be thrown "The source contains no DataRows"
You could do something like so;
DataTable resultDT = result.Any() ? result.CopyToDataTable<DataRow>() : myDataTable.Clone();
But that will run the query twice (also as Tim pointed out).
Therefore you could convert that to a list object using (.ToList()), check the count and do your processing then. That has performance implications in such that you create a new instance of the object (List object).
Doing a try/catch with attempt to convert it to DataTable also isn't a good idea. See Pranays answer for another great way to achieve the final result.
Related
I have some problem with my code. I want to replace the ForEach loop with the help of LINQ here, is there any way or solution to solve my problem? My code is given bellow.
static public string table2Json(DataSet ds, int table_no)
{
try
{
object[][] tb = new object[ds.Tables[table_no].Rows.Count][];
int r = 0;
foreach (DataRow dr in ds.Tables[table_no].Rows)
{
tb[r] = new object[ds.Tables[table_no].Columns.Count];
int col = 0;
foreach (DataColumn column in ds.Tables[table_no].Columns)
{
tb[r][col] = dr[col];
if ((tb[r][col]).Equals(System.DBNull.Value))
{
tb[r][col] = "";
}
col++;
}
r++;
}
string table = JsonConvert.SerializeObject(tb, Formatting.Indented);
return table;
}
catch (Exception ex)
{
tools.log(ex.Message);
throw ex;
}
}
This question really asks 3 different things:
how to serialize a DataTable
how to change the DataTable serialization format and finally
how to replace nulls with empty strings, even though an empty string isn't a NULL.
JSON.NET already handles DataSet and DataTable instance serialization with a DataTableConverter whose source can be found here. You could just write :
var str = JsonConvert.SerializeObject(data);
Given this DataTable :
var dataTable=new DataTable();
dataTable.Columns.Add("Name",typeof(string));
dataTable.Columns.Add("SurName",typeof(string));
dataTable.Rows.Add("Moo",null);
dataTable.Rows.Add("AAA","BBB");
You get :
[{"Name":"Moo","SurName":null},{"Name":"AAA","SurName":"BBB"}]
DataTables aren't 2D arrays and the column names and types matter. Generating a separate row object with named fields is far better than generating an object[] array. It also allows makes it far easier for clients to handle the JSON string without knowing its schema in advance. With an object[] for each row, the clients will have to know what's stored in each location in advance.
If you want to use a different serialization format, you could customize the DataTableConverter. Another option though, is to use DataRow.ItemArray to get the values as an object[] and LINQ to get the rows, eg :
object[][] values=dataTable.Rows.Cast<DataRow>()
.Select(row=>row.ItemArray)
.ToArray();
Serializing this produces :
[["Moo",null],["AAA","BBB"]]
And there's no way to tell which item is the name and which is the surname any more.
Replacing DBNulls with strings in this last form needs an extra Select() to replace DBNull.Value with "" :
object[][] values=dataTable.Rows.Cast<DataRow>()
.Select(row=>row.ItemArray
.Select(x=>x==DBNull.Value?"":x)
.ToArray())
.ToArray();
Serializing this produces :
[["Moo",""],["AAA","BBB"]]
That's what was asked, but now we have no way to tell whether the Surname is an empty string, or just doesn't exist.
This may sound strange, but Arabic names may be one long name without surname. Makes things interesting for airlines or travel agents that try to issue tickets (ask me how I know).
We can get rid of ToArray() if we use var :
var values=dataTable.Rows.Cast<DataRow>()
.Select(row=>row.ItemArray
.Select(x=>x==DBNull.Value?"":x));
JSON serialization will work the same.
LINQ is not a nice fit for this sort of thing because you are using explicit indexes r and col into multiple "array structures" (and there is no easy/tidy way to achieve multiple, parallel enumeration).
Other issues
tb is repeatedly newed, filled with data and then replaced in the next iteration, so you end up capturing only the last row of input to the JSON string - that's a logical bug and won't work as I think you intend.
The inner foreach loop declares but does not use the iteration variable column - that's not going to break anything but it is redundant.
You will get more mileage out of using JSON.Net properly (or coding the foreach loops as for loops instead if you want to navigate the structures yourself).
I want to be able to get a datarow from a datarow array where the datarow has the maximum value for a specified column in all of the datarows.
I imagine it would follow suit to this LINQ statement
Dim minRow As DataRow = myDataRows.AsEnumerable.Min(Function(row) row.Field(Of Decimal)("myColumnName"))
However the Max function returns a decimal instead of a datarow. Is there some easy syntax that handles such a query for me?
And before anyone asks, I tag c# since in winforms they can easily be converted between each other using a website.
If you can use MoreLINQ, this can easily be done using MaxBy():
var maxRow = myDataRows.MaxBy(r => r.Field<Decimal>("myColumnName"));
But using regular Linq:
var minRow = myDataRows.OrderBy(r => r.Field<decimal>("myColumnName")).FirstOrDefault();
var maxRow = myDataRows.OrderBy(r => r.Field<decimal>("myColumnName")).LastOrDefault();
A little late but I want to share it anyway ;), you can do it in O(n) instead of O(n*log n) (which is the order for OrderBy extension method) if you use Aggregate extension method:
var minRow =myDataRows.Aggregate(myDataRows[0],(seed,next)=>next.Field<decimal>("myColumnName").Field<decimal>("myColumnName")?next:seed);
Check if your myDataRows array have at least one row
I have a small problem, which I just cannot find how to fix it.
For my data table, Dictionar.DtDomenii, I need to copy the unique data from my other data table, Dictionar.Dt.
I wrote a query, but when using query.CopyToDataTable() to copy the data into my DtDomenii table, the "CopyToDataTable" function does not show...
Am I doing something wrong? Is there an easier way to copy distinct data (categories from my example) from one data table to another?
PS: I've already read the information from MSDN https://msdn.microsoft.com/en-us/library/bb386921%28v=vs.110%29.aspx
void LoadCategories()
{
var query = (from cat in Dictionar.dt.AsEnumerable()
select new
{
categorie = categorii.Field<string>("Categoria")
}).Distinct();
// This statement does not work:
Dictionar.dtDomenii = query.CopyToDataTable();
}
Only collections of DataRows can use the CopyToDataTable method. For example:
DataTable table = new DataTable();
table.AsEnumerable().CopyToDataTable(); // this works
List<DataRow> dataRows = new List<DataRow>();
dataRows.CopyToDataTable(); // this also works
List<string> strings = new List<string>();
strings.CopyToDataTable(); // this does not work
The select new... part of your query is converting the DataRows into objects. You need to convert the objects back into DataRows before you can use the CopyToDataTable method.
You might have better luck doing something like this:
DataTable copy = Dictionar.dt
.AsEnumerable() // now an enumerable of DataRow
.Distinct() // remove duplicates, still an enumerable of DataRow
.CopyToDataTable(); // done!
You can also make a complete copy of the table with Dictionar.dt.Copy(), then remove the duplicate rows manually.
'cannot implicitly convert type string to data row[]'.
Is it possible to store the string type to data row[]? I need to store the value of the particular column in that particular data row array. Suggest me an answer please.
DataRow[] drprocess = objds.Tables[0].Rows[i]["ProcessName"].ToString();
You have declared a variable of type DataRow[] called drProcess but have not yet created an array of DataRows in which to put any values. Instead you've tried to tell the compiler that the string you're retrieving is actually a DataRow, which it isn't.
It's possible that what you want to do is to create your array of DataRows, then create a DataRow object and assign it into the array. However, I'm suspicious that this isn't actually what you're trying to achieve. Note that objds.Tables[0].Rows is already a collection of DataRows. You can actually edit or use this collection yourself if you need.
Or if you're wanting to create a new collection of process names you might be better creating a var processes = new List<string>() then calling process.Add(objds.Tables[0].Rows[i]["ProcessName"].ToString()).
It all depends what you want to do with this collection of process names afterwards.
First, a DataRow always belongs to a DataTable. To which table should these new DataRow belong? I will presume objds.Tables[0].
I also assume that you have a string-column and you want to split every field in it to a DataRow[], then we need to know the delimiter.
Presuming it is a comma:
DataRow[] drprocess = objds.Tables[0].Rows[i].Field<string>("ProcessName").Split(',')
.Select(name => {
DataRow row = objds.Tables[0].NewRow();
row.SetField("ProcessName", name);
return row;
})
.ToArray();
I am fairly new to LINQ and am trying to apply this query on a datatable called "EmpInfo" to sort it.
var sortedRows = (from myRow in EmpInfoDS.Tables["EmpInfo"].AsEnumerable()
orderby myRow["EmpID"] ascending
select myRow).ToArray();
This works. The next thing I am trying to do is to copy the results into the SAME datatable.
EmpInfoDS.Tables["EmpInfo"].Clear();
EmpInfoDS.Tables["EmpInfo"] = sortedRows.CopyToDataTable();
The second line throws the following error:
"Property or indexer 'System.Data.DataTableCollection.this[string]'
cannot be assigned to -- it is read only"
Please some one tell me how to deal with this. And if there is another way please tell me.
The error says that you can't assign the table using indexers because Tables is a readonly property. So to solve the problem:
EmpInfoDS.Tables.Remove("EmpInfo");
DataTable dt = sortedRows.CopyToDataTable();
dt.TableName = "EmpInfo";
EmpInfoDS.Tables.Add(dt);
I think this is how it can be done: (you might prefer a different LoadOption)
EmpInfoDS.Tables["EmpInfo"].Clear();
sortedRows.CopyToDataTable(EmpInfoDS.Tables["EmpInfo"], LoadOption.OverwriteChanges);
This should also work:
EmpInfoDS.Tables["EmpInfo"].Clear();
foreach (var row in sortedRows)
EmpInfoDS.Tables["EmpInfo"].Rows.Add(row);
With LINQ, you have to watch out for deferred execution. Basically, queries are not executed until the data from the query is consumed. If you want a query to execute immediately, use the ToList() method.
var sortedRows = EmpInfoDS.EmpInfo.OrderBy(e => e.EmpID).ToList();
Next, the code is straight forward:
var employees = EmpInfoDS.EmpInfo;
employees.Clear();
foreach (var sortedRow in sortedRows)
employees.Add(sortedRow);
EmpInfoDS.SaveChanges();