I have two DataTables: allRows and rowsToDelete. I want to delete rows from allRows that rowsToDelete contains. Both tables have the same structure (same columns and their datatypes), but I can't know exact column names and even their quantity.
object.Equals() method recognizes rows from different tables as not equal so I can't use this approach.
From googling and reading StackOverflow I got an idea that probably it can be even done in one line, but I don't know how to build condition for this case:
allRows = allRows.AsEnumerable().Where(???).CopyToDataTable();
I don't know the structure of your data but generally speaking you can do
var cleanedUp = allRows.Where(row => !rowsToDelete.Any(row2 => row2.Id == row.Id ));
If you don not have any primary key then check multiple columns that combinations(composite key) provide you uniqueness on the behalf of which you can easily delete rows from all rows having similar data exist in rowsTodelete table.
OR
First implement inner join among two tables on similar column for retrieving data that you want to delete and insert that into any temp table and delete this data from allrows table.
Hope that will help you.
Your task has no solution, and you change the conditions,
first you write:
Both tables have the same structure (same columns and their datatypes)
then you write:
That's the problem - structure can be different and there can be no "Id" column
If you can even change the data, you can subtract both tables and investigate their structure, columns, etc. But you insist that you do not know them.
When you find out the structure, the task becomes elementary, smth like this(i use my structure from my own project):
var foo = DbContext
.Set<Task>()
.Select(x => new{x.Assignee, x.Availability})
.ToList();
var foo2 = DbContext
.Set<Task2>()
.Select(x => x)
.ToList();
var bar = foo2.Where(x => foo.Select(y => y.Assignee).Contains(x.Assignee)
&& foo.Select(y => y.Availability).Contains(x.Availability));
DbContext.RemoveRange(bar);
DbContext.SaveChanges();
you can write more elegantly, but it's so obvious
This solution works for any data structure, any columns' number and types.
We present DataRows as arrays and compare each cell.
public static void DeleteCopies(DataTable allRows, DataTable rowsToDelete)
{
foreach (DataRow rowToDelete in rowsToDelete.Rows)
{
foreach (DataRow row in allRows.Rows)
{
var rowToDeleteArray = rowToDelete.ItemArray;
var rowArray = row.ItemArray;
bool equalRows = true;
for (int i = 0; i < rowArray.Length; i++)
{
if (!rowArray[i].Equals(rowToDeleteArray[i]))
{
equalRows = false;
}
}
if (equalRows)
{
allRows.Rows.Remove(row);
break;
}
}
}
}
Related
I was wondering if it was possible to store the results of multiple linq queries in a single IQueryable statement?
I have a query which I use in a foreach:
//Where OnDemandHistory is the table
IOrderedQueryable<OnDemandHistory> A;
foreach (int id in machineID)
{
A = OnDemandHistory.Where(c => c.MachineID == id).OrderByDescending(c => c.ODHisDate);
// I want to Order all results before writing to the table
foreach(var entry in A)
{
// I add to a table based on all entries found in A
}
}
I am trying to get all entries where the machine ID match. The no. of MachineID's is varying (based on the user).
I was wondering if I can do a OrderByDescending after I have stored all the results from the query but before adding to the table.
I know due to the inner foreach loop that it won't happen, however when I try to do this:
foreach (int id in machineID)
{
A = OnDemandHistory.Where(c => c.MachineID == id).OrderByDescending(c => c.ODHisDate);
// I want to Order all results before writing to the table
}
foreach(var entry in A)
{
// I add to a table based on all entries found in A
}
I get a local variable A uninitialized error,
How would I go about solving this?
Thanks in advance
You can do it much simpler by using the Contains statement:
var result = OnDemandHistory.Where(c => machineID.Contains(c.MachineID))
.OrderByDescending(c => c.ODHisDate);
The error is caused because as the final result of your first query produces only the result of the last value of machineID this may result in either a null result or an uninitialisedvalue of A, so A needs to be initialised. Also, I suspect A could be a simple list.
You need something like:
A = new List<OnDemandHistory>();
foreach (int id in machineID)
{
A.AddRange(OnDemandHistory
.Where(c => c.MachineID == id).OrderByDescending(c => c.ODHisDate).ToList());
}
// order A here
Then run your second loop having checked that A has rows. However, I suspect there are smarter ways in LINQ of concatenating the machineID part of the query as a single LINQ statement.
I am trying to create a new row when I have the filtered results of a linq query. I want the syntax to be something like the following, but I don't know what to do. I am trying to create a new row with a selection of different fields from the old row.
dv = (from row in MV.Data.AsEnumerable()
where !(row["eCode"].ToString().Contains("OP"))
select DataRow(row["time"], row["value"],
row["size"], row["condition"],
row["eCode"]))
.CopyToDataTable().DefaultView;
Without knowing your specific application, you might try something like this:
//where dt is your DataTable...
var source = MV.Data;
var newRows = source.Where(d => d.eCode.ToString().Contains("OP"))
.Select(d => dt.Rows.Add(d.time, d.value, d.size, d.condition, d.eCode));
I write most of my linq like this as I find the lambdas easier to read. Note that DataTables accepts a list of parameters as an input; no need to construct a DataRow first. The first Where clause checks for your eCode, and select adds the row to your DataTable AND returns it to newRows in case you still need them. If you don't need them, you can either iterate through using ForEach or, if there are few enough rows, do this:
//where dt is your DataTable...
var source = MV.Data;
source.Where(d => d.eCode.ToString().Contains("OP"))
.ToList() //copies everything into memory
.Foreach(d => dt.Rows.Add(d.time, d.value, d.size, d.condition, d.eCode));
This approach just incorporates an inline Foreach iteration.
The following C# code takes a large datatable with many columns and an array of 2 column names. It will give a new datatable with two rows where there are duplicate rows for the two fields supplied staff no & skill.
This is too specific and I need to supply any number of fields as the groupby.
can someone help me?
string[] excelField = new string[0]; // contains a list of field name for uniquness
excelField[0] = "staff No";
excelField[1] = "skill";
DataTable dataTableDuplicateRows = new DataTable();
dataTableDuplicateRows.Clear();
dataTableDuplicateRows.Columns.Clear();
foreach (string fieldName in excelField)
{
dataTableDuplicateRows.Columns.Add(fieldName);
}
var duplicateValues = dataTableCheck.AsEnumerable()
.GroupBy(row => new { Field0 = row[excelField[0]], Field1 = row[excelField[1]] })
.Where(group => group.Count() > 1)
.Select(g => g.Key);
foreach (var duplicateValuesRow in duplicateValues)
{
dataTableDuplicateRows.Rows.Add(duplicateValuesRow.Field0, duplicateValuesRow.Field1);
}
I think what you require is something make the linq more dynamic, even though you could achieve it by using expression tree, the DynamicLinq library would appear to solve your issue in an easier way.
For you case, with the library, just use the GroupBy extension method with a string value.
More info about DynamicLinq library:
Scott Gu's blog
I'm looping through the items in my database using C# .NET and I'm attempting to display different data dependant on if a column value matches any of the values in an array. Because my array could potentially have hundreds of values, I'd rather not create hundreds of different IF statements, if possible. Is there a simpler way to achieve this?
Here's some example code, where "Items" is my db data and "Categories" is a column of said data:
var categoryList = new List<int> { 1, 2, 3, 4 };
foreach(var item in Items){
if(item.Categories.Any(x => #categoryList.Equals(x))){
<p>Yes</p>
}else{
<p>No</p>
}
}
The answer I give is based on the answer of this question. I modified the code to your situation.
foreach(var item in Items)
{
bool hasCategory = categoryList.Any(x => item.Categories.Any(c => c.Id == x));
}
or for larger collections (performance-wise):
bool hasCategory = item.Categories.Select(c => c.Id)
.Intersect(categoryList)
.Any();
Edit:
At first I thought item.Categories was a collection of IDs or something but then I started doubting. If item.Categories is just a single integer, following code will work:
foreach(var item in Items)
{
if(categoryList.Any(x => x == item.Categories))
<p>Yes</p>
else
<p>No</p>
}
I have a List. I would like to filter through all the rows in the list of tables to find all the rows that are in every datatable in the list.
If possible, the compare needs to be on the "ID" column that is on every row.
I have tried to solve this with Linq but got stuck. This is what I have so far:
List<DataTable> dataTables = new List<DataTable>();
// fill up the list
List<DataRow> dataRows =
dataTables.SelectMany(dt => dt.Rows.Cast<DataRow>().AsEnumerable()).
Aggregate((r1, r2) => r1.Intersect(r2));
Any suggestions?
Not a simple question. Here's a solution (which seems too complicated to me, but it works).
Obtain the Id value from each row using Linq to DataSets
Intersect the multiple lists to find all the common values
Find a single occurence of a row in all of the rows that have one of the matching ids
To use Linq on DataTable, see this article for a start.
You could get the ids from one table like this
var ids = dt.AsEnumerable().Select (d => d.Field<int>("ID")).OfType<int>();
and from multiple tables
var setsOfIds = dataTables.Select (
t => t.AsEnumerable().Select (x => x.Field<int>("ID")).OfType<int>());
To intersect multiple lists, try this article. Using one of the methods there you could obtain the intersection of all of the ids.
Using Jon Skeet's helper method
public static class MyExtensions
{
public static List<T> IntersectAll<T>(this IEnumerable<IEnumerable<T>> lists)
{
HashSet<T> hashSet = new HashSet<T>(lists.First());
foreach (var list in lists.Skip(1))
{
hashSet.IntersectWith(list);
}
return hashSet.ToList();
}
}
we can write
var commonIds = setsOfIds.InsersectAll();
Now flatten all the rows from the DataTables and filter by the common ids:
var rows = dataTables.SelectMany (t => t.AsEnumerable()).Where(
r => commonIds.Contains(r.Field<int>("ID")));
Now group by id and take the first instance of each row:
var result = rows.GroupBy (r => r.Field<int>("ID")).Select (r => r.First ());
Try this to find the intersection between the two lists:
r1.Join(r2, r1 => r1.Id, r2 => r2.Id, (r1, r2) => r1);