LINQ select items from set A which are not in set B - c#

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:
The type or namespace name 'ColumnsInDGV' could not be found (are you missing a using directive or an assembly reference?)
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?

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.

Related

Is it possible to remove columns in select query

As the question suggests I want to remove the columns from a select query where in that column are empty.
var query = from a in ...
select new
{
A =(decimal?)null,
B =(decimal?)null,
C = a.Amount1
};
var query2 = from b in ...
select new
{
A = b.Amount2,
B = b.Amount3,
C = (decimal?)null
};
var query3 = query.Concat(query2);
Output:
query3=[0]{A=null, B=null, C=100.00}
[1]{A=100.00, B=50.25, C=null}
Expected Result:
query3=[0]{C=100.00}
[1]{A=100.00, B=50.25}
You can't do this. The result set has to contain items of the same type and even if fields are null they still have to be there.
You could not show them in your UI, but exactly how you do that will depend on the UI.
You can't. A class has a predefined set of fields (not to speak about ExpandoObject which has some compiler tricks going on). This is the same for anonymous type, which you use.
You can't just hide or remove fields which are not filled. What if you iterate over the instances and try to retrieve item.C, which was null and thus removed? That would normally give you a compiler error. How would .NET resolve that?
The only other thing you can do is put in two different types in your list (a list of objects, so untyped), a very bad idea in my opinion. Keep it like this. You could add an indicator which type the row is, to be able to test it easily.
So:
select new
{
Type = "A", // or "B"
A =(decimal?)null,
B =(decimal?)null,
C = a.Amount1
};
A LINQ query typically outputs objects of a common type, so each object has to have the same base type (and the columns associated. The only way to concatenate objects of different types is to cast them to object:
var query = from a in ...
select new
{
C = a.Amount1
};
var query2 = from b in ...
select new
{
A = b.Amount2,
B = b.Amount3,
};
var query3 = query.Cast<object>().Concat(query2.Cast<object>());
but since they're anonymous you'll have to use dynamic to access them since you won;t be able to cast back to the original type, so you'll end up with something like this:
Console.WriteLine(((dynamic)(query3[0])).C);
Console.WriteLine(((dynamic)(query3[1])).A);
or at best:
dynamic list = query3.ToList();
Console.WriteLine(list[0].C);
Console.WriteLine(list[1].A);
but in any case you'll lose compile-time type safety.

getting a type mismatch/cast error I don't understand when using array Concat

I'm trying to use the array Concat method to combine a literal, hard-coded array of a type called SearchDef.Column with an array returned by a static function on my class. The static function also returns SearchDef.Column[]. This is the basis for an end user query tool which combines a number of built-in fields with end-user defined fields. So, I want to combine a literal array with one returned by a function, and I want the presentation to be seamless between the built-in and user-defined fields. I've left out a lot of the surrounding code because it's a lot, and I don't think it helps with the question.
I'm getting the familiar "are you missing a cast?" error when I use Concat to combine the results of my static function with the literal array.
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<AdamOneilSoftware.SearchDef.Column>' to 'AdamOneilSoftware.SearchDef.Column[]'. An explicit conversion exists (are you missing a cast?)
This doesn't make sense to me because the main Columns property (at the top) is of type SearchDef.Column[]. The static function SearchDef.CustomFieldsFromQuery also returns SearchDef.Column[], so I don't understand why the compiler is calling it IEnumerable. Indeed I tried casting the result of CustomFieldsFromQuery directly, but was left with the same error. I also tried putting .ToArray() on the end -- no change.
I hope I've given enough source code .... try not to get hung up on the string literals/SQL fragments. In an of themselves, they aren't the problem. Rather it's some kind of array type mismatch that I don't follow.
Columns = new Column[] {
new Column("[p].[Number]", "Number"),
new Column("[p].[Name] AS [PatientName]", "Name"),
... ommisions for clarity ...
new Column("[mc].[Number] AS [Microchip]", "Microchip")
}.Concat(SearchDef.CustomFieldsFromQuery( // FAILS HERE
"SELECT [Name] AS [Header], 'dbo.Patient' + [ccft].[StorageColumn] + ... omitted for clarity
"WHERE [fd].[ClinicID]=#0 AND [fd].[ShowInSearchResults]=1 AND [fd].[IsActive]=1"));
Here's the code for the CustomFieldsFromQuery method:
public static Column[] CustomFieldsFromQuery(string query)
{
// query must return 3 columns: Header, Expression, ID and have a formatting placeholder {0} where the ID will be inserted into expression
Hs4Db db = new Hs4Db();
DataTable tbl = db.SelectTable(query, SqlDb.Params(db.CurrentClinicID), CommandType.Text);
return tbl.AsEnumerable().Select(dataRow => new Column(dataRow)).ToArray();
}
I think you're misdiagnosing the issue.
You are attempting to assign an IEnumerable to it. Your code is effectively:
Columns = (IEnumerable<Column>)someValue;
You should change this to :
Columns = ((IEnumerable<Column>)someValue).ToArray();
Putting this into your code gives:
Columns = new Column[] {
new Column("[p].[Number]", "Number"),
new Column("[p].[Name] AS [PatientName]", "Name"),
... ommisions for clarity ...
new Column("[mc].[Number] AS [Microchip]", "Microchip")
}.Concat(SearchDef.CustomFieldsFromQuery( // FAILS HERE
"SELECT [Name] AS [Header], 'dbo.Patient' + [ccft].[StorageColumn] + ... omitted for clarity
"WHERE [fd].[ClinicID]=#0 AND [fd].[ShowInSearchResults]=1 AND [fd]. [IsActive]=1"))
.ToArray() // Note the ToArray on the *outside*...;
Concat returns an IEnumerable<AdamOneilSoftware.SearchDef.Column> and the Columns property you assign it to is probably a AdamOneilSoftware.SearchDef.Column[].
Append a .ToArray() after your Concat call and it will work fine.

Linq grouping query to DataTable

Trying to return a Linq group by query into a DataTable. Getting the error
Cannot implicitly convert type
'System.Collections.Generic.IEnumerable<AnonymousType#1>' to
'System.Collections.Generic.IEnumerable<System.Data.DataRow>'. An
explicit conversion exists (are you missing a cast?)
The I am querying a DataTable named Vendors where the data would be as follows:
Vendor Name
654797 Lowes
897913 Home Depot
800654 Waffle House
The Vendor is stored as char(6) in the DB and name as char as well... don't ask me why, I just work here :)
DataTable VendorsDT = New DataTable();
DataColumn VenName = VendorsDT.Columns.Add("Name", typeof (string));
DataColumn VenCode = VendorsDT.Columns.Add("Vendor", typeof(string));
IEnumerable<DataRow> Vendors = from row in alertsDT.AsEnumerable()
group row by new { Name = row.Field<string>("Name"), Vendor = row.Field<string>("Vendor") } into z
select new
{
Name = z.Key.Name,
Vendor = z.Key.Vendor,
};
VendorsDT = Vendors.CopyToDataTable();
This is not going to work, as you are projecting your original query of alertsDT into an anonymous type, which would not be able to be referenced by your IEnumerable<DataRow> Vendor variable, because your query is not a sequence of DataRows.
Given that you are performing a grouping, and that you have also already set up your VendorsDT table with the desired columns, the path of least resistance is to fix your Vendor variable type (use var for type inference) and then loop over the result to populate your second table.
var Vendors = /* your unchanged query omitted */
foreach (var item in Vendors)
{
VendorsDT.Rows.Add(item.Name, item.Vendor);
}
As a note, I've used your variable names, although it is convention in C# to use lower case letters to start local variable names, with upper case typically left for method names, properties, etc. So you would favor vendors over Vendors and vendorsDT (or vendorsTable) over VendorsDT, for example.
This is a more linq way of doing
var query = from row in alertsDT.AsEnumerable()
group row by new { Name = row.Field<string>("Name"), Vendor = row.Field<string>("Vendor") } into z
select new
{
Name= z.Key.Name,
Vendor= z.Key.Vendor,
};
VendorsDT = query.CopyToDataTable();
Here define the extension method 'CopyToDataTable()' as specified in the following MSDN article.
The CopyToDataTable method takes the results of a query and copies the data into a DataTable, which can then be used for data binding. The CopyToDataTable methods, however, only operate on an IEnumerable source where the generic parameter T is of type DataRow. Although this is useful, it does not allow tables to be created from a sequence of scalar types, from queries that project anonymous types, or from queries that perform table joins.

How to group then select elements into new class in LINQ (C# preferably)

Hi I am trying to get my head around grouping, and then building my own class in the result. I know the result of a group by is an IGrouping collection but can I access the rows as they are being built to add a couple of flags to them with a custom class?
I have a class called FlightTimes with some data, but I'd like to append some data to the rows, like a FlagRedEye. So I created a class called FlightTimeResult with the original FlightTime class data plus the flag.
Can I do this? I can't seem to figure out how to get it to work. I like to use strong types until I understand what is going on. I had to change a few things to protect my client so I apologize for any syntax errors.
IGrouping<string, FlightTimeResult> FlightTimes =
( from flighttimes in schedules.FlightTimes
group flighttimes by flighttimes.FlightType.ToString()
into groupedFlights
select new FlightTimeResult( )
{
FlightTimeData = FlightTime, // Original class data
FlagRedEye = (FlightTime.departureTime.Hour >= 0 &&
FlightTime.departureTime.Hour < 6) // Extra flag
} )
The goal is to have a collection of FlightTimesResult (FlightTime + extra flag) grouped by FlightType. Not sure how to access the individual FlightTime rows in the query 'select new FlightTimeResult()'
Do i need to use a nested query on the groupedFlights?
Thank you very much.
It is easiest achieved by calling Linq functions explicitly in following way:
IQueryable<IGrouping<string, FlightTimeResult>> query
= schedules.FlightTimes.GroupBy(
ft => ft.FlightType.ToString(), // key
ft => new FlightTimeResult() { // your constructed objects for key
FlightTimeData = ft,
FlagRedEye = (ft.departureTime.Hour >= 0 && ft.departureTime.Hour < 6)
}
);
The two-argument GroupBy operator function takes two lambdas as arguments - one for extracting keys, second for extracting values for it.
Also keep in mind that group by operation (be it group itm by key construction or GroupBy call) returns a collection of IGrouping<,>s - not a single one.
Thus it will be IEnumerable<IGrouping<,>> or IQueryable<IGrouping<,>>.
I think you're on the right track. Instead of grouping FlightTimes by FlightType, try building FlightTimeResults and grouping those by FlightType instead:
var results =
from ft in schedules.FlightTimes
group new FlightTimeResult
{
FlightTimeData = ft,
FlagRedeye = ft.DepartureTime.Hour >= 0 && ft.DepartureTime.Hour < 6
}
by ft.FlightType.ToString()
into groupedFlights
select groupedFlights;

How to change a value without new projection in LINQ?

I have the following LINQ query:
var source = from node in MyNods
select new
{
Id = node.Id,
Name = node.Name,
ParentId = node.ParentId, // Nullable
};
In the query above, the ParentId is nullable. Now I need a new result which is match the first one but with small change that if ParentId is null I want it to be 0.
I wrote this:
var source2 = from s in source
select new
{
Id = s.Id,
Name = s.Name,
ParentId = s.ParentId ?? 0, // Just change null values to 0
};
Can I implement that with a simpler way (I mean without the new projection) ?
Edit: The new projection is the same of the first one and both ParentId are nullable.
LINQ isn't ideal for executing side-effects on an existing collection. If that's what you want to do, you'd be better off doing:
foreach(var node in MyNods)
{
if(!node.ParentId.HasValue)
node.ParentId = 0;
}
If that's not the case, you're going to have to project. Your existing query is perfectly fine; the only way I can think of shortening it is:
var source2 = from s in source
select new
{
s.Id, s.Name,
ParentId = s.ParentId ?? 0
};
EDIT:
It appears you're trying to create an instance of a different type (i.e. with virtually the same properties as the source but with one specific property being non-nullable), so you can't escape creating instances of the new type and copying properties over. You might want to consider writing a 'real' (non-anonymous) type that represents what you want and get the source-type to provide a conversion-method. Then you can do:
var source2 = source.Select(s => s.ToNonNullableParentVersion());
EDIT:
From your edit, it now appears that you don't need a different type to represent the projected data since the 'coalesced' property is still meant to be nullable. If you don't want to mutate the existing collection and you don't like your current query, your best bet would still be to write a conversion method in the source-type.

Categories

Resources