VB.NET Returns IEnumerable(Of IEnumerable(Of T)) when Using Group By LINQ Statement - vb.net

I am trying to convert Anthyme Caillard's INotifyDataErrorInfo implementation into VB.NET. All goes well until I reach the Validate method, which has the following LINQ query, in which q is of type IEnumerable<IGrouping<string, ValidationResult>>:
var q = from r in validationResults
from m in r.MemberNames
group r by m into g
select g;
I have tried the following translation:
Dim q = From r In valResults
From m In r.MemberNames
Group r By r.MemberNames Into Group
Select Group
Or even the lambda expression version, (assuming I'm using John Skeet's syntax correctly):
Dim q = valResults.GroupBy(Function(r) r, Function(r) r.MemberNames)
but in both of these, q is of type IEnumerable(Of IEnumerable(Of ValidationResult)).
I have looked at VB and IGrouping for LINQ Query and VB.Net - Linq Group By returns IEnumerable(Of Anonymous Type), but I don't think they apply since I will be working with the group directly, and not a specialized class.
Why aren't these implementations returning the same thing, and what can I do to make q the required IEnumerable(Of IGrouping(Of String, ValidationResult))?

It seems that the VB compiler treats LINQ query syntax different than the C# compiler.
In C# the expression
from element in list
group element by element.Value
is identical to
list.GroupBy(element => element.Value)
In VB however
From element In list
Group element By element.Value Into g = Group
is translated into
list.GroupBy(Function(element) element.Value,
Function(key, element) New With { Key .Value = key, Key .g = element })
I can't see any good reason why the VB compiler introduces an anonymous type here - but that's what it does.
I recommend you always translate C# LINQ query syntax (from a in list select a.Value) into a method chain (list.Select(a => a.Value)). That way the VB compiler can't interfere. It is forced to use the exact chain of methods.
However your translation of the original query isn't correct.
var q = from r in validationResults
from m in r.MemberNames
group r by m into g
select g;
is actually translated into
var q = validationResults.SelectMany(r => r.MemberNames, (r, m) => new { r, m })
.GroupBy(t => t.m, t => t.r);
which in VB becomes:
Dim q = validationResults.SelectMany(Function(r) r.MemberNames,
Function(r, m) New With { Key .r = r, Key .m = m }) _
.GroupBy(Function(t) t.m, Function(t) t.r)

Related

LINQ Does not contain a definition for 'union'

what is wrong with this linq query that show me the error of Does not contain a definition for 'union'
(from rev in db.vM29s
where Years.Contains(rev.FinancialYear) && rev.Exclude=="No"
group rev by new { rev.RevenueCode, rev.FinancialYear } into g
select new
{
Revenuecode = g.Key.RevenueCode,
totalRevenue = g.Sum(x => x.RevenueAmount),
RevenueEnglish = (from a in db.RevenueCodes where a._RevenueCode == g.Key.RevenueCode select a.RevenueEng).FirstOrDefault(),
// MinorCode = (from a in db.MinorCodes where a._MinorCode == g.Key.MinorCode select a.MinorEng),
RevenueDari = (from a in db.RevenueCodes where a._RevenueCode == g.Key.RevenueCode select a.RevenueDari).FirstOrDefault(),
Yearss = g.Key.FinancialYear
}
)
.Union
(from u in db.rtastable
where Years.Contains(u.Year)
group u by new { u.objectcode, u.Year } into g
select new
{
Revenuecode = g.Key.objectcode,
totalRevenue = g.Sum(x => x.amount),
RevenueEnglish = (from a in db.RevenueCodes where a._RevenueCode == g.Key.objectcode select a.RevenueEng).FirstOrDefault(),
// MinorCode = (from a in db.MinorCodes where a._MinorCode == g.Key.MinorCode select a.MinorEng),
RevenueDari = (from a in db.RevenueCodes where a._RevenueCode == g.Key.objectcode select a.RevenueDari).FirstOrDefault(),
Yearss = g.Key.Year
}
)
.ToList();
If you included using System.Linq; and both Anonymous Types have exactly the same property names + property types, then what you did should work.
Yet it does not work. The solution is to check your Anonymous Types for subtle property name differences and/or subtle property type differences.
E.g. even an int vs a smallint or double or decimal will cause this build error:
'System.Collections.Generic.IEnumerable<AnonymousType#1>' does not contain a definition for 'Union' and the best extension method overload 'System.Linq.Queryable.Union(System.Linq.IQueryable, System.Collections.Generic.IEnumerable)' has some invalid arguments
Switching to .Concat() will not fix this: it has the same (obvious) restriction that the types on both sides must be compatible.
After you fix the naming or typing problem, I would recommend that you consider switching to .Concat(). The reason: .Union() will call .Equals() on all objects to eliminate duplicates, but that is pointless because no two Anonymous Objects that were created independently will ever be the same object (even if their contents would be the same).
If it was your specific intention to eliminate duplicates, then you need to create a class that holds your data and that implements .Equals() in a way that makes sense.
You should use Concat or using addRange if the data is allready in memory.

Issue utilizing OrderBy With Neo4jClient

I'm sorting a large database by the quantity of outgoing relationships. I have a working Cypher query as follows:
optional match (n)-[r]->(m)
return n, Count(r) as c
order by c DESC limit 1;
The Cypher query is working as anticipated. However, as I am debugging and stepping through the Cypher -> Neo4jClient conversion, I cannot seem to find the root of the issue.
public ReturnPayload[] getByConnections()
{
var query = client.Cypher
.OptionalMatch("(p)-[r]->(m)")
.Return((p, r, m, c) => new
{
p = p.As<Person>(),
pid = (int)p.Id(),
e = r.As<RelationshipInstance<Object>>(),
m = m.As<Metadata>(),
c = r.Count()
}).OrderByDescending("c").Limit(1);
var res = query.Results;
var payload = new List<ReturnPayload>();
foreach (var el in res)
{
var t = new ReturnPayload();
t.e = el.e;
t.m = el.m;
t.pid = el.pid;
t.p = el.p;
payload.Add(t);
}
return payload.ToArray<ReturnPayload>();
}
I suspect that a part of the problem may be that I am not utilizing CollectAs<T>() and thus it is referring a count of '1' per each person. Unfortunately, I have attempted using CollectAs<T>() and CollectAsDisctinct<T>() and my resultant JSON architecture is only wrapping each individual element in an array, as opposed to aggregating identical elements into an array proper.
The FOREACH loop is there to assist in converting from the anonymous type into my relatively standard <ReturnPayload> which does not utilize a c object within its parameters.
Your time is appreciated, thank you.
Debugged Query Test:
OPTIONAL MATCH (p)-[r]->(m)
RETURN p AS p, id(p) AS pid, r AS e, m AS m, count(r) AS c
ORDER BY c DESC
LIMIT {p0}
And my 'functional' Cypher query:
optional match (n)-[r]->(m)
return n, Count(r) as c
order by c DESC limit 1;
So, you've identified yourself that you're running two different queries. This is the issue: your C# does not match the Cypher you're expecting.
To make the C# match the working Cypher, change it to this:
var query = client.Cypher
.OptionalMatch("(n)-[r]->(m)")
.Return((n, r) => new
{
n = n.As<Person>(),
c = r.Count()
})
.OrderByDescending("c")
.Limit(1);
That should now produce the same query, and thus the same output as you're expecting from the working Cypher.
On a purely stylistic note, you can also simplify your foreach query to a more functional equivalent:
var payload = query
.Results
.Select(r => new ReturnPayload {
n = r.n,
c = r.c
})
.ToArray();
return payload;
However, as I look closer as your query, it looks like you just want the count for the sake of getting the top 1, then you're throwing it away.
Consider using the WITH clause:
optional match (n)-[r]->(m)
with n as n, count(r) as c
order by c desc
limit 1
return n
You can map that back to C# using similar syntax:
var query = client.Cypher
.OptionalMatch("(n)-[r]->(m)")
.With((n, r) => new
{
n = n.As<Person>(),
c = r.Count()
})
.OrderByDescending("c")
.Limit(1)
.Return(n => new ReturnPayload // <-- Introduce the type here too
{
n = n.As<Person>()
});
Then, you don't need to query for data and just throw it away with another foreach loop. (You'll notice I introduce the DTO type in the Return call as well, so you don't have to translate it out of the anonymous type either.)
(Disclaimer: I'm just typing all of this C# straight into the answer; I haven't double checked the compilation, so my signatures might be slightly off.)
Hope that helps!

Unable to cast the type 'System.String' to type 'System.Collections.Generic.IEnumerable

My query below works fine when there is only category to return but as soon as as there is more than one I get Sequence contains more than one element error message. I would like to return all the relevant categories. So I changed the PostCategory in the DTO from string to a List and that is when I get the casting error. I also tried changing it to a IList(of String) and IList(of be_Category) and adding ToList to ca.CategoryName. That didn't work.
My query with the joins:
Public Function SelectByID(id As Integer) As PostDTO Implements IPostRepository.SelectByID
Dim post As New PostDTO
Using db As Ctx = New Ctx
post = From ca In db.be_Categories
Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
Join p In db.be_Posts On c.PostID Equals (p.PostID)
Where p.PostRowID = id
Select New PostDTO With {
.PostCategory = ca.CategoryName,
.PostDateCreated = p.DateCreated,
.PostGuid = p.PostID,
.PostId = p.PostRowID,
.PostText = p.PostContent,
.PostTitle = p.Title}).Single
End Using
Return post
End Function
So is it possible to project the sequence of Category Names into a new DTO or something else or is there another way to return all the categories? I guess since CategoryName is a string, L2E cannot project the strings into the list. Do I need a GroupBy to project category strings into a new form? I also tried AsEnumerable and I tried String.Join - neither worked.
The DTO is below - If PostCategory is a string then I can get a single category back to the view. I hope I have explained it clearly.
Public Class PostDTO
Public PostId As Integer
Public PostGuid As Guid
Public PostTitle As String
Public PostSummary As String
Public PostText As String
Public PostDateCreated As DateTime
Public PostIsPublished As Boolean
Public PostCategory As IList(Of be_PostCategory)
End Class
EDIT:
Updated SelectById:
Public Function SelectByID(id As Integer) As IEnumerable(Of PostDTO) Implements IPostRepository.SelectByID
Dim post As IEnumerable(Of PostDTO)
Using db As Ctx = New Ctx
post = From ca In db.be_Categories
Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
Join p In db.be_Posts On c.PostID Equals (p.PostID)
Where p.PostRowID = id
Select New PostDTO With {
.PostCategory = ca.CategoryName,
.PostDateCreated = p.DateCreated,
.PostGuid = p.PostID,
.PostId = p.PostRowID,
.PostText = p.PostContent,
.PostTitle = p.Title}).ToList
End Using
End Function
The Single method throws an exception when the number of elements returned by the query is not exactly 1. Try removing .Single() from the end of your query.
Also, we don't see what variable it's being assigned to. An anonymous type works well here, though if you don't use one, make sure it's correct i.e.
Dim result As IEnumerable(Of PostDTO) = From ca In db.be_Categories ...
Edit #1
I should add some clarification. When running a LINQ query, expect for the query to return any number of results, just like you would expect SQL or similar to do. In the case where you only expect one result however (i.e. Select Top 1 ...) then you can use .Single(). Getting back to your case, your query is against an Entity Framework data source I can only imagine (Ctx is that, correct?). As indicated by the MSDN documentation, you will return a DbQuery(Of PostDTO), which is the data type returned by a LINQ to Entities query against a DbContext. This type, depending on what you want to do with it, can be cast to several interfaces. See its definition
Public Class DbQuery(Of TResult) _
Implements IOrderedQueryable(Of TResult), IQueryable(Of TResult), _
IEnumerable(Of TResult), IOrderedQueryable, IQueryable, IEnumerable, _
IListSource, IDbAsyncEnumerable(Of TResult), IDbAsyncEnumerable
The IEnumberable(Of TResult), with TResult being a PostDTO in your case, is what you can cast to so you can enumerate the results, providing a lot of functionality like further queries, sorting, getting average, max, min, etc. Hope this clears it up.
Edit #2
Finally getting to the bottom of the problem. The first part gives you a single Post, but with nothing in the PostCategory. The second part puts the list of categories into that single post.
post = From ca In db.be_Categories
Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
Join p In db.be_Posts On c.PostID Equals (p.PostID)
Where p.PostRowID = id
Select New PostDTO With {
.PostCategory = Nothing,
.PostDateCreated = p.DateCreated,
.PostGuid = p.PostID,
.PostId = p.PostRowID,
.PostText = p.PostContent,
.PostTitle = p.Title}).FirstOrDefault()
post.PostCategory = (From ca In db.be_Categories
Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
Where p.PostRowID = id
Select ca.CategoryName).ToList()

Create New Distinct List(of T) from Existing List(of T) Using LINQ

How can I get a new distinct list from an existing list using LINQ? This is what I have so far and it is not distinct but does give me a new list.
Dim tmpQryColumn = (From a In _allAudits
Select New CheckBoxListItem
With {.Id = a.AuditColumn, .Name = a.AuditColumn}
).Distinct()
_columnList = New List(Of CheckBoxListItem)(tmpQryColumn)
I suspect the problem is that CheckboxListItem doesn't override Equals/GetHashCode so any two instances are effectively distinct. Try:
Dim columns = (From a In _allAudits
Select a.AuditColumn).Distinct()
_columnList = (From column in columns
Select New CheckBoxListItem
With {.Id = column, .Name = column}
).ToList()
I'm sure there's a simpler way of writing it in VB, but I'm not familiar enough with the syntax to write it. The C# version would be:
_columnList = _allAudits.Select(x => x.AuditColumn)
.Distinct()
.Select(x => new CheckboxListItem { Id = x, Name = x })
.ToList();
The Distinct extension method relies on the types in question properly implementing value equality. The most likely reason that it's failing here is that CheckBoxListItem doesn't implement equality based on the Id and Name properties.
To fix this do one of the following
Implement equality semantics for CheckBoxListItem
Create an IEqualityComparer<CheckBoxListItem> and use the overload of Distinct which takes that value

How do I return the column names of a LINQ entity

I am using LINQ to SQL queries to return data in my application. However I find it is now needful for me to return the column Names. Try as I might I have been completely unable to find out how to do this on the internet.
So if my LINQ entity table has the properties (Last_Name, First_name, Middle_Name) I need to return:
Last_name
First_Name
Middle_name
rather than the usual
Smith
John
Joe
You could certainly do it with some LINQ-To-Xml directly against the ".edmx" file or the embedded model resources in the compiled assembly.
The below query gets the field (not column) names. If you need the columns then just change the query to suit.
var edmxNS = XNamespace.Get(#"http://schemas.microsoft.com/ado/2007/06/edmx");
var schemaNS = XNamespace.Get(#"http://schemas.microsoft.com/ado/2006/04/edm");
var xd = XDocument.Load(#"{path}\Model.edmx");
var fields =
from e in xd
.Elements(edmxNS + "Edmx")
.Elements(edmxNS + "Runtime")
.Elements(edmxNS + "ConceptualModels")
.Elements(schemaNS + "Schema")
.Elements(schemaNS + "EntityType")
from p in e
.Elements(schemaNS + "Property")
select new
{
Entity = e.Attribute("Name").Value,
Member = p.Attribute("Name").Value,
Type = p.Attribute("Type").Value,
Nullable = bool.Parse(p.Attribute("Nullable").Value),
};
Lets assume you're talking about the Contact Table in the assembly named YourAssembly in a Context called MyDataContext
Using Reflection against a Table
You can use reflection to get the properties like you would any type
var properties = from property in
Type.GetType("YourAssembly.Contact").GetProperties()
select property.Name
;
foreach (var property in properties)
Console.WriteLine(property);
As shaunmartin notes this will return all properties not just Column Mapped ones. It should also be noted that this will return Public properties only. You'd need to include a BindingFlags value for the bindingAttr Parameter of GetProperties to get non-public properties
Using the Meta Model
You can use the Meta Model System.Data.Linq.Mapping to get the fields ( I added IsPersistant to only get the Column Mapped properties)
AttributeMappingSource mappping = new System.Data.Linq.Mapping.AttributeMappingSource();
var model = mappping.GetModel(typeof (MyDataContext));
var table = model.GetTable(typeof (Contact));
var qFields= from fields in table.RowType.DataMembers
where fields.IsPersistent == true
select fields;
foreach (var field in qFields)
Console.WriteLine(field.Name);
Using Reflection from a query result
If on the other hand you wanted it from a query result you can still use reflection.
MyDataContextdc = new MyDataContext();
Table<Contact> contacts = dc.GetTable<Contact>();
var q = from c in contacts
select new
{
c.FirstName,
c.LastName
};
var columns = q.First();
var properties = (from property in columns.GetType().GetProperties()
select property.Name).ToList();
I stumbled upon this answer to solve my own problem and used Conrad Frix 's answer. The question specified VB.NET though and that is what I program in. Here are Conrad's answers in VB.NET (they may not be a perfect translation, but they work):
Example 1
Dim PropertyNames1 = From Prprt In Type.GetType("LocalDB.tlbMeter").GetProperties()
Select Prprt.Name
Example 2
Dim LocalDB2 As New LocalDBDataContext
Dim bsmappping As New System.Data.Linq.Mapping.AttributeMappingSource()
Dim bsmodel = bsmappping.GetModel(LocalDB2.GetType())
Dim bstable = bsmodel.GetTable(LocalDB.tblMeters.GetType())
Dim PropertyNames2 As IQueryable(Of String) = From fields In bstable.RowType.DataMembers
Where fields.IsPersistent = True
Select fields.Member.Name 'IsPersistant to only get the Column Mapped properties
Example 3
Dim LocalDB3 As New LocalDBDataContext
Dim qMeters = From mtr In LocalDB3.tblMeters
Select mtr
Dim FirstResult As tblMeter = qMeters.First()
Dim PropertyNames3 As List(Of String) = From FN In FirstResult.GetType().GetProperties()
Select FN.Name.ToList()
To display the results:
For Each FieldName In PropertyNames1
Console.WriteLine(FieldName)
Next
For Each FieldName In PropertyNames2
Console.WriteLine(FieldName)
Next
For Each FieldName In PropertyNames3
Console.WriteLine(FieldName)
Next
Please also read Conrad's answer for notes on each method!