Search code examples
c#linqexcept

LINQ select items from set A which are not in set B


I would like to perform an Except operation on set of items.

Code is like this:

IEnumerable<DataGridViewColumn> dgvColumns = dataGridView.Columns.OfType<DataGridViewColumn>();
IEnumerable<DataColumn> dsColumns = dataSet.Tables[0].Columns.OfType<DataColumn>();

Now, how to select Columns from dataSet.Tables[0] which are not in dgvColumns? I know that Columns from DataGridView are different type than Columns in DataSet. I want to pick up only a subset of common values. Like this:

        var ColumnsInDGV = from c1 in dgvColumns
                           join c2 in dsColumns on c1.DataPropertyName equals c2.ColumnName
                           select new { c1.HeaderText, c1.DataPropertyName, c2.DataType, c1.Visible };

Above code selects me "columns" that are in both sets. So I tought I will create another set of "columns" that are in DataSet:

  var ColumnsInDS = from c2 in dsColumns select new { HeaderText = c2.ColumnName, DataPropertyName = c2.ColumnName, c2.DataType, Visible = false };

and now that I will be able to perfrom Except like this:

var ColumnsOnlyInDS = ColumnsInDS.Except<ColumnsInDGV>;

But I am getting two errors:

  1. The type or namespace name 'ColumnsInDGV' could not be found (are you missing a using directive or an assembly reference?)
  2. Cannot assign method group to an implicitly-typed local variable

So the solution would be to build a class and then use it instead of implictly - typed local variable. But I think that developing a class only for this reason is a not necessery overhead.

Is there any other solution for this problem?


Solution

  • You've almost got it. You just need to write:

    // use () to pass a parameter
    // type (should) be inferred
    var ColumnsOnlyInDS = ColumnsInDS.Except(ColumnsInDGV);
    

    instead of:

    // do not use <> - that passes a type parameter;
    // ColumnsInDGV is not a type
    var ColumnsOnlyInDS = ColumnsInDS.Except<ColumnsInDGV>;
    

    Update: So, the above actually doesn't work because Except depends on comparing items in two sequences for equality; obviously, your anonymous type has not overriden object.Equals and so each object that you create of this type is treated as a distinct value. Try this* instead:

    var dgvColumns = dataGridView.Columns.Cast<DataGridViewColumn>();
    var dsColumns = dataSet.Tables[0].Columns;
    
    // This will give you an IEnumerable<DataColumn>
    var dsDgvColumns = dgvColumns
        .Where(c => dsColumns.Contains(c.DataPropertyName))
        .Select(c => dsColumns[c.DataPropertyName]);
    
    // Then you can do this
    var columnsOnlyInDs = dsColumns.Cast<DataColumn>().Except(dsDgvColumn);
    

    *Note: Where in the above expression for dsDgvColumns makes more sense than SkipWhile because it will apply the specified filter over all results. SkipWhile would only apply the filter as long as it was true and would then stop applying it. In other words it would work if your DataGridViewColumn not bound to your DataSet were at the beginning of the DataGridView; but not if it were in the middle or at the end.