I am trying to convert some code from VB over to C# and have had an extremely hard time with this particular LINQ query. First I'll show the classes:
Public Class CompanyData
Property CompanyID As String = ""
Property VendorNumber As String = ""
Property VendorName As String = ""
End Class
Public Class MultiCompData
Inherits CompanyData
Property ExpCompanyId As String = ""
End Class
Then in the VB example what it does is Group all into MultiCompData in a multiCompGroup field, foreach through it and add to a different list of CompanyData if ExpCompanyId is null/empty. Code:
Sub ProcessMultiCompData(multiCompDataJEs As IEnumerable(Of MultiCompData), ByRef multiCompData As List(Of CompanyData), ByRef undeterminedDists As List(Of CompanyData))
Dim multiCompGroups =
From m In multiCompDataJEs
Group m By m.ExpCompanyId,
m.VendorNumber,
m.VendorName
Into Group
For Each grp In multiCompGroups
If grp.ExpCompanyId = "" Then
undeterminedDists.AddRange(grp.Group)
In the foreach, grp has the following properties - ExpCompanyId, VendorNumber, VendorName and Group (which is the part I can't replicate in C#) Group being an IEnumerable. Is it not possible to add that IEnumerable in C# to multiCompGroups?? I have tried:
var multiCompGroups = from m in multiCompDataJEs group by new {m.ExpCompanyiD, m.VendorNumber, m.VendorName} into multiGroups
multiGroups doesn't show up as an IEnumerable in my grp value when I get to the foreach, i've also tried it this way:
var multiCompGroups = multiCompDataJEs.GroupBy(x => new {m.ExpCompanyId, m.VendorNumber, m.VendorName}).Select(y => new {y.Key.ExpCompanyId, y.Key.VendorNumber, y.Key.VendorName}).ToList();
Both are producing the same thing. I have the 3 properties show up when I do grp. but the "Into Group" (grp.Group in the VB code above) doesn't show up in C#. Is it even possible to do?
The C# you're looking for is:
var multiCompGroups =
from m in multiCompDataJEs
group m by new {m.ExpCompanyiD, m.VendorNumber, m.VendorName} into multiGroups
select multiGroups;
foreach(var grp in multiCompGroups)
{
if(grp.Key.ExpCompanyId == "")
{
undeterminedDists.AddRange(grp);
}
}
Related
I am trying to convert some C# code to VB.NET from the following tutorial that shows how to generate a HTML table with row group headings using LINQ Group By Into:
https://ole.michelsen.dk/blog/grouping-data-with-linq-and-mvc/
The C# code works as advertised, but my VB.NET conversion has an issue.
The working C# line of code I'm having trouble with in VB.NET:
// Group the books by Genre
var booksGrouped = from b in books
group b by b.Genre into g
select new Group<string, Book> { Key = g.Key, Values = g };
Visual Studio is reporting "g.Key" is:
string IGrouping<string, Book>.Key
In VB.NET, the two classes, Group and Book look like this:
Public Class Group(Of K, T)
Public Property Key As K
Public Property Values As IEnumerable(Of T)
End Class
Public Class Book
Public Title As String
Public Author As String
Public Genre As String
Public Price As Decimal
End Class
And the above C# line of code as VB.NET:
Dim booksGrouped = From b In books Group b By b.Genre Into g = Group
Select New Group(Of String, Book) With {.Key = g.Key, .Values = g}
Note: I had to add the "= Group" to the 'Into' clause otherwise "g" has the error "BC36594: Definition of method 'g' is not accessible in this context."
Visual Studio is reporting that "g.Key" is now an IEnumerable:
BC30456: 'Key' is not a member of 'IEnumerable(of Book)'.
So C# is seeing the IGroup interface, and VB.NET is seeing an IEnumerable.
I love the simplicity of the solution offered in the tutorial and would really like to implement this in VB.NET, does anyone have any ideas how to get this working?
Many thanks.
The sample data in VB.NET is
Dim books As New List(Of Book)
books.Add(New Book With {.Author = "Douglas Adams", .Title = "The Hitchhiker's Guide to the Galaxy", .Genre = "Fiction", .Price = 159.95D})
books.Add(New Book With {.Author = "Scott Adams", .Title = "The Dilbert Principle", .Genre = "Fiction", .Price = 23.95D})
books.Add(New Book With {.Author = "Douglas Coupland", .Title = "Generation X", .Genre = "Fiction", .Price = 300D})
books.Add(New Book With {.Author = "Walter Isaacson", .Title = "Steve Jobs", .Genre = "Biography", .Price = 219.25D})
books.Add(New Book With {.Author = "Michael Freeman", .Title = "The Photographer's Eye", .Genre = "Photography", .Price = 195.5D})
You need to declare who is going to be your key. By your code is supposed Genre. So, you need to use grouping functions as Morton suggest or you have to identify a “key” for your grouped List(of Book)
The code below shows how:
Dim booksGrouped = From b In books
Group b By Key = b.Genre Into g = Group
Select New Group(Of String, Book) With {.Key = Key, .Values = g}
For Each current In booksGrouped
For Each value In current.Values
With value
Console.WriteLine("Key:" & current.Key & "value: " & Strings.Join({ .Genre, .Author, .Price, .Title}, " - "))
End With
Next
Next
You don't need to be explicit about the key - the C# code specifies "g.Key", but C# is figuring out that it must be "Genre":
Dim booksGrouped = From b In books
Group b By b.Genre Into g = Group
Select New Group(Of String, Book) With {
.Key = Genre,
.Values = g
}
I'm having some issues construction a LINQ query to find MATCHING values in two IEnumerables I have from CSV files and outputting those matching values to another list for some bookkeeping later on in my application.
My classes for the IEnumerables and the related code (using CSVHelper) to read the CSVs into the IEnumerables are below. Any input on where to begin, LINQ query wise, to find those matching values and output to a list? I'm relatively new to LINQ (usually use SQL in the backend) and I'm finding it a bit difficult to do exactly what I want to.
CLASSES:
class StudentSuccessStudents
{
[CsvColumn(Name ="StudentID", FieldIndex = 1)]
public string StudentID { get; set; }
}
class PlacementStudents
{
[CsvColumn(Name = "StudentId", FieldIndex = 1)]
public string StudentId { get; set; }
}
PROGRAM:
CsvFileDescription inputCsvStuSuccess = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true,
EnforceCsvColumnAttribute = false
};
CsvContext ccStuSuccess = new CsvContext();
CsvFileDescription inputCsvStuScores = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = false,
EnforceCsvColumnAttribute = true
};
CsvContext ccStuScores = new CsvContext();
IEnumerable<StudentSuccessStudents> students = ccStuSuccess.Read<StudentSuccessStudents>(filePath, inputCsvStuSuccess);
IEnumerable<PlacementStudents> outputStudents = ccStuScores.Read<PlacementStudents>(csvPath, inputCsvStuScores);
Any suggestions on how to get all my "StudentID" fields in the first list that match a "StudentId" in the second one to output to another list with LINQ? I basically need to have that "matching" list so I can safely ignore those values elsewhere.
You could always use the SQL syntax of Linq like shown below. This way it looks more like something you're use to, and gets you the duplicate values you need. Also looks more readable (in my opinion) too.
var duplicates = from success in students
join placement in outputStudents on success.StudentID equals placement.StudentId
select success.StudentID;
This is a linq operator to do this functionality called Intersect so
If they were both the same type you could do this
var result = students.Intersect(outputStudents);
Which should be the fastest way.
Since they are different types you do this
var result = students.Intersect(outputStudents.Select(x => new StudentSuccessStudent(x.id) );
basically you create a new list of the correct type dynamically
This is an example where inheritance or interfaces are powerful. If they both inherited from the same type then you could intersect on that type and C# would solve this problem super fast.
I have this list:
Spain_Finance
France_Sport
Spain_Politics
USA_Science
USA_Finance
I use this code to check if the list contains a determinate value:
Dim namecountry As String = "Spain"
Dim listcontainscountry As Boolean = mylistofcountries.Any(Function(l) l.Contains(namecountry))
If listcontainscountry = True Then
' Here I need to get the elements from the list that contains Spain
End If
So after doing this inside the if statement I need to get the elements from the list that contains Spain
The result will be this:
Spain_Finance
Spain_Politics
I looking for a simple code to do this, I can do a foreach and compare the name from country with the item list, but I want to know is there another way more simple, this is for learn, I appreciate your contribution, thanks
You can use Where instead for Any so the code will be like the following:
Dim namecountry As String = "Spain"
Dim listcontainscountry = mylistofcountries.Where(Function(l) l.Contains(namecountry)).ToList()
Since the question is tagged to c# also, this Example will help you, that is the code will be :
List<string> countryList = new List<string>() { "Spain_Finance", "France_Sport", "Spain_Politics", "USA_Science", "USA_Finance" };
string namecountry = "Spain";
List<string> SelectedCountries = countryList.Where(x => x.Contains(namecountry)).ToList();
if(SelectedCountries.Count>0)
Console.WriteLine("Selected Countries : {0}", String.Join(",",SelectedCountries));
else
Console.WriteLine("No Matches ware found");
update: You can use Select followed by Where and .SubString the code will be like this
List<string> SelectedCountries = countryList.Where(x => x.Contains(namecountry))
.Select(x=>x.Substring(x.IndexOf("_")+1))
.ToList();
XML Snippet
<GameList>
<Game>
<GameDefinitiongameID>{1b2e4ff7-ddd3-4fe3-9fdf-6b2ee240d18c}</GameDefinitiongameID>
<WMID>{01bc5c01-923d-4efb-bba6-49e93b2694ab}</WMID>
<VersionNumberversionNumber>1.0.0.0</VersionNumberversionNumber>
<Title>JamesBond007:Nightfire</Title>
<GameExecutablepath>\easupport\go_ez.exe</GameExecutablepath>
<GameExecutablepath>\easupport\jamesbond007nightfire_code.exe</GameExecutablepath>
<GameExecutablepath>\easupport\jamesbond007nightfire_ereg.exe</GameExecutablepath>
<GameExecutablepath>\easupport\jamesbond007nightfire_ez.exe</GameExecutablepath>
<GameExecutablepath>jamesbond007nightfire_uninst.exe</GameExecutablepath>
<GameExecutablepath>unwise.exe</GameExecutablepath>
<RatingratingID>{CEC5DB5A-B4C9-4809-96C6-39CE715E4790}</RatingratingID>
<ratingSystemID>{36798944-B235-48ac-BF21-E25671F597EE}</ratingSystemID>
<DescriptordescriptorID>{F110F831-9412-40c9-860A-B489407ED374}</DescriptordescriptorID>
<RatingratingID>{18CD34B7-7AA3-42b9-A303-5A729B2FF228}</RatingratingID>
<ratingSystemID>{768BD93D-63BE-46A9-8994-0B53C4B5248F}</ratingSystemID>
<DescriptordescriptorID>{B54162A2-F67F-46dc-9ED5-F6067520EC94}</DescriptordescriptorID>
<DescriptordescriptorID>{BE562A5F-2A80-4c28-9752-74C696E2ABAF}</DescriptordescriptorID>
</Game>
and so on.
I used the following to query this file
Dim members = From m In GameXml.Element("GameList").Elements("Game")
Where m.Element("Title").Value.ToLower.Contains(Name) = True
Select m.Descendants
"Name" is the variable containing the search string.
Now, how to I get all the info (as array of strings or any other parse-able format) from the "members" variable.
I am new to XML and LINQ.
I am OK with both C# and VB.Net.
If you don't know what exact properties it has you can do this:
dynamic members = from m In GameXml.Element("GameList").Elements("Game")
where m.Element("Title").Value.ToLower.Contains(Name) = True
select m;
now you can use: members.ratingSystemID
But if you know the properties you can create a class like:
public class Game{
public string Title{set; get; }
public string Name {get; set;}
.
.
.
}
and then:
List<Game> members = from m In GameXml.Element("GameList").Elements("Game")
where m.Element("Title").Value.ToLower.Contains(Name) = True
select new {Title = m.Title, Name = m.Name, ...};
so you can use it like Members[0].Title
Assuming you want to get Game node, you're so close:
Dim members = From m In GameXml.Element("GameList").Elements("Game")
Where m.Element("Title").Value.ToLower.Contains(Name) = True
Select m.Descendants("Game")
or (C#):
var members = GameXml.Descendants("Game")
.Where(g=>g.Element("Title").Value.ToLower().Contains("Whatever"));
assuming we use this on a table using "select * from table " ?
i want to create an array of classes
that use filed names (of the datatable) as class field names initalise them with the datatable
fields
is it possible ?
if so please help :)
soory for the spelling
my writen english is not that good
but i hope i was clear
This is a great time to use anonymous types
var query = (from row in DataTable.Rows.Cast<DataRow>()
select new
{
ID = row.Field<int>("ID"),
Name = row.Field<string>("Caption"),
\\etc etc etc
}).ToArray();
If you already have a class to store the results you could just as easily replace the anonymous type with that class (or use a Tuple in 4.0)
If you do this frequently you could write an extension method that uses reflection to match up data columns with the names of properties in the class array you are trying to generate
that use filed names (of the datatable) as class field names initalise them with the datatable fields
The only way to do that would be to generate a class at runtime, which is (a) not easy and (b) usually not very useful because the code that will use this class won't know the names of the class members.
You could use the dynamic features of C# 4 to build a list of ExpandoObjects:
Func<DataRow, dynamic> projection =
r =>
{
IDictionary<string, object> exp = new ExpandoObject();
foreach (DataColumn col in r.Table.Columns)
exp[col.ColumnName] = r[col];
return exp;
};
dynamic[] objects = dataTable.AsEnumerable().Select(projection).ToArray();
...
dynamic o = objects[0];
int id = o.Id;
string name = o.Name;
But then you loose the benefits of strong typing...
I think a more interesting idea would be to project the data rows to existing classes, based on the member names and data column names:
public T DataRowToObject<T>(DataRow row) where T : new()
{
var properties = typeof(T).GetProperties().Where(p => p.CanWrite);
var columns = row.Table.Columns.Cast<DataColumn>();
// Match properties of T with DataTable columns
var common =
from p in properties
from c in columns
where c.ColumnName == p.Name
&& p.PropertyType.IsAssignableFrom(c.DataType)
select p;
T obj = new T();
foreach (var prop in common)
{
if (!row.IsNull(prop.Name))
{
object value = row[prop.Name];
prop.SetValue(obj, value, null);
}
}
return obj;
}
...
MyClass[] objects = dataTable.AsEnumerable().Select(row => DataRowToObject(row)).ToArray();