LINQ to DATASET with Group Join and Group By (vb.net) - vb.net

I've got 2 datatables and am trying to summarize the data in them using a left outer join. The join works fine with this code
Dim Journal = From entries In dt.AsEnumerable()
Join inccodes In dtGL.AsEnumerable()
On entries.Field(Of String)("GLCode") Equals inccodes.Field(Of String)("GLCode")
Group By keys = New With {Key .IncomeCode = entries.Field(Of String)("GLCode"), Key .IncomeDesc = .inccodes.Field(Of String)("GLCodeDesc")}
Into ChargeSum = Group, sm = Sum(entries.Field(Of Decimal)("Amount"))
Where sm <> 0
Select New GL_Journal With {.IncomeCode = keys.IncomeCode, .IncomeDesc = keys.IncomeDesc, .LineAmount = sm}
`
However, since I really want a Left Outer Join I want to use Group Join instead of Join.
As soon as I change the Join to Group Join the code in the Group by at ".inccodes.field(Of String)("GLCodeDesc")" has ".inccodes" highlighted with the error "'inccodes' is not a member of 'anonymous type'"
I've reviewed much documentation on Group By and Group Join but there is scant information on them together.
Any ideas? Would I have more options/success with the method syntax?

If i try to reproduce your query using a left outer join, I will do something like this :
Dim dt As New DataTable
dt.Columns.Add("GLCode", GetType(String))
dt.Columns.Add("Amount", GetType(Decimal))
dt.Rows.Add("111", 3251.21)
dt.Rows.Add("222", 125.79)
dt.Rows.Add("999", 10000)
Dim dtGL As New DataTable
dtGL.Columns.Add("GLCode", GetType(String))
dtGL.Columns.Add("GLCodeDesc", GetType(String))
dtGL.Rows.Add("111", "a")
dtGL.Rows.Add("222", "b")
dtGL.Rows.Add("333", "c")
Dim Journal = From entries In dt.AsEnumerable()
Group Join inccodes In dtGL.AsEnumerable()
On entries.Field(Of String)("GLCode") Equals inccodes.Field(Of String)("GLCode")
Into Group
From lj In Group.DefaultIfEmpty()
Group By keys = New With {Key .IncomeCode = entries.Field(Of String)("GLCode"), Key .IncomeDesc = lj?.Field(Of String)("GLCodeDesc")}
Into ChargeSum = Group, sm = Sum(entries.Field(Of Decimal)("Amount"))
Select New With {.IncomeCode = keys.IncomeCode, .IncomeDesc = keys.IncomeDesc, .LineAmount = sm}

Related

LINQ, Join, Group By and SUM

In this VB.NET query, how can I get access to the fields in the new statement?
Dim query =
From t1 In tbl1
Join t2 In tbl2 On t1.CAMPAIGNID Equals t2.CAMPAIGNID
Group By t1.CAMPAIGNID Into Group
Select New With {
.id = CAMPAIGNID,
.CALLS = Group.Sum(Function(a) t2.CALLS),
.count = Group.Count(Function(a) t1.TERMCD = "Refused")
}
You can access the fields via the passed in parameter a. The parameter a is of the anonymous type you created via New With {...}
Dim query =
From t1 In tbl1
Join t2 In tbl2 On t1.CAMPAIGNID Equals t2.CAMPAIGNID
Group By t1.CAMPAIGNID Into Group
Select New With {
.id = CAMPAIGNID,
.CALLS = Group.Sum(Function(a) a.t2.CALLS),
.count = Group.Count(Function(a) a.t1.TERMCD = "Refused")
}

Using LINQ to Entity - How can I join/concatenate columns from another table

I am having some issues with trying to figure out the correct way, or syntax, to join/concatenate a series of "name" columns from a separate table into a query.
Currently I am testing in LINQpad using two queries; the first returns all the master data that I use for other background work, and the second is a user-friendly version that I bind to a DGV. The issue comes in when I try to join the Physicians names like I do for a separate combobox.
This is what I have thus far - while it does return the Physician's name, it will NOT return the name if the TITLE field is NULL on the Physicians table.
Dim query1 = (From demog In data_Demogs
From MedHist In data_Demog_MedHists.where(Function(a) demog.ID_Demog = a.ID_Demog).defaultifempty
From BGLAssay In data_Demog_BGLs.where(Function(a) demog.ID_Demog = a.ID_Demog).defaultifempty
Select
demog.ID_Demog,
demog.Last_Name,
demog.First_Name,
demog.ID_Demog_AKA,
demog.DOB,
demog.Gender,
demog.ST_Complete,
demog.LT_Complete,
demog.LT_Due_Date,
demog.ID_Physician,
demog.ID_Reason_For_Call,
demog.Intl_Patient,
demog.Mayo_Patient,
MedHist.ID_Disease_Group,
MedHist.ID_Disease_Type,
BGLAssay.ID_BGL_Assay)
Dim query2 = (From items In query1
From demogAKA In data_Demogs.Where(Function(a) items.ID_Demog = a.ID_Demog_AKA).defaultifempty
From DType In tbl_Disease_Types.Where(Function(a) items.ID_Disease_Type = a.ID_Disease_Type).defaultifempty
From DGroup In tbl_Disease_Groups.Where(Function(a) items.ID_Disease_Group = a.ID_Disease_Group).defaultifempty
From RFC In tbl_Reason_For_Calls.Where(Function(a) items.ID_Reason_For_Call = a.ID_Reason_For_Call).defaultifempty
From Phys In tbl_Physicians.Where(Function(a) items.ID_Physician = a.ID_Physician).defaultifempty
From Title In tbl_Titles.Where(Function(a) Phys.ID_Title = a.ID_Title).defaultifempty
Select
items.ID_Demog,
items.Last_Name,
items.First_Name,
AKA_Name = demogAKA.Last_Name + ", " + demogAKA.First_Name,
items.DOB,
items.Gender,
items.ST_Complete,
items.LT_Complete,
items.LT_Due_Date,
DType.Disease_Type_Abr,
DGroup.Disease_Group_Name,
RFC.Reason_For_Call,
items.ID_Physician,
Phys_Name = Phys.Last_Name + ", " + Phys.First_Name + ", " + Title.Title
).distinct
console.writeline(Query2)
This is the currently query I for a combobox that DOES bring back all names, joining those names even if a field is NULL.
Dim Phys = (From e In tbl_Physicians
Group Join f In tbl_Titles On e.ID_Title Equals f.ID_Title
Into Matched = Group
From m In Matched.DefaultIfEmpty()
Select e.ID_Physician,
e.Last_Name,
e.First_Name,
e.Middle_Initial,
m.Title
).ToArray().Select(Function(item) New With {
.ID = item.ID_Physician,
.Phys_Name = (String.Join(", ",
String.Join(",",
New String() {item.Last_Name, item.First_Name, item.Title}).Split(
New Char() {","}, System.StringSplitOptions.RemoveEmptyEntries)))
})
Console.writeline(Phys)
When I try to add a third query to return just the Physician's name, and join that to the final query, I get the following error:
Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.
'Query 1 removed to save space
Dim PhysNames = (From e In tbl_Physicians
Group Join f In tbl_Titles On e.ID_Title Equals f.ID_Title
Into Matched = Group
From m In Matched.DefaultIfEmpty()
Select e.ID_Physician,
e.Last_Name,
e.First_Name,
e.Middle_Initial,
m.Title
).ToArray().Select(Function(item) New With {
.ID = item.ID_Physician,
.Phys_Name = (String.Join(", ",
String.Join(",",
New String() {item.Last_Name, item.First_Name, item.Title}).Split(
New Char() {","}, System.StringSplitOptions.RemoveEmptyEntries)))
})
Dim query2 = (From items In query1
From demogAKA In data_Demogs.Where(Function(a) items.ID_Demog = a.ID_Demog_AKA).defaultifempty
From DType In tbl_Disease_Types.Where(Function(a) items.ID_Disease_Type = a.ID_Disease_Type).defaultifempty
From DGroup In tbl_Disease_Groups.Where(Function(a) items.ID_Disease_Group = a.ID_Disease_Group).defaultifempty
From RFC In tbl_Reason_For_Calls.Where(Function(a) items.ID_Reason_For_Call = a.ID_Reason_For_Call).defaultifempty
From Phys In PhysNames.Where(Function(a) items.ID_Physician = a.ID).defaultifempty
Select
items.ID_Demog,
items.Last_Name,
items.First_Name,
AKA_Name = demogAKA.Last_Name + ", " + demogAKA.First_Name,
items.DOB,
items.Gender,
items.ST_Complete,
items.LT_Complete,
items.LT_Due_Date,
DType.Disease_Type_Abr,
DGroup.Disease_Group_Name,
RFC.Reason_For_Call,
items.ID_Physician,
Phys.Phys_Name
).distinct
console.writeline(Query2)
When I try to join my working query into the final query, I get the following error:
Invalid cast from 'System.String' to 'VB$AnonymousDelegate_0`2[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934...
'Query1 removed to save space
Dim query2 = (From items In query1
From demogAKA In data_Demogs.Where(Function(a) items.ID_Demog = a.ID_Demog_AKA).defaultifempty
From DType In tbl_Disease_Types.Where(Function(a) items.ID_Disease_Type = a.ID_Disease_Type).defaultifempty
From DGroup In tbl_Disease_Groups.Where(Function(a) items.ID_Disease_Group = a.ID_Disease_Group).defaultifempty
From RFC In tbl_Reason_For_Calls.Where(Function(a) items.ID_Reason_For_Call = a.ID_Reason_For_Call).defaultifempty
From Phys In tbl_Physicians
Where items.ID_Physician = Phys.ID_Physician
Group Join f In tbl_Titles On Phys.ID_Title Equals f.ID_Title
Into Matched = Group
From m In Matched.DefaultIfEmpty()
Select
items.ID_Demog,
items.Last_Name,
items.First_Name,
AKA_Name = demogAKA.Last_Name + ", " + demogAKA.First_Name,
items.DOB,
items.Gender,
items.ST_Complete,
items.LT_Complete,
items.LT_Due_Date,
DType.Disease_Type_Abr,
DGroup.Disease_Group_Name,
RFC.Reason_For_Call,
items.ID_Physician,
PhysName = Function(a) String.Join(", ",
String.Join(",",
New String() {Phys.Last_Name, Phys.First_Name, m.Title}).Split(
New Char() {","}, System.StringSplitOptions.RemoveEmptyEntries))
).distinct
console.writeline(Query2)
After a long time playing around in LINQpad, and then finally re-reading JM's answer to a former question I had, I realized what I was doing wrong.
As per his post:
The problem is that, while LINQ in general has no issue with that code, LINQ to Entities does. LINQ syntax is the same for every provider but the implementation under the hood differs and, in the case of LINQ to Entities, your LINQ code has to translated to SQL and, in this case, there's no mapping from String.Join to SQL. That code would work fine with LINQ to Objects so one solution is to push that operation out of the original query and into a LINQ to Objects query. That would mean selecting the raw data with your LINQ to Entities query, calling ToList or ToArray on the result to materialise the query, then performing another query on that result. That second query will be LINQ to Objects rather than LINQ to Entities and so String.Join will not be an issue.
So... Once I realized I needed to push out the String.Join, I ended up with the following code:
Dim DispList = (From items In MastList
From demogAKA In dbACL.data_Demog.Where(Function(a) items.ID_Demog = a.ID_Demog_AKA).DefaultIfEmpty
From DType In dbACL.tbl_Disease_Type.Where(Function(a) items.ID_Disease_Type = a.ID_Disease_Type).DefaultIfEmpty
From DGroup In dbACL.tbl_Disease_Group.Where(Function(a) items.ID_Disease_Group = a.ID_Disease_Group).DefaultIfEmpty
From RFC In dbACL.tbl_Reason_For_Call.Where(Function(a) items.ID_Reason_For_Call = a.ID_Reason_For_Call).DefaultIfEmpty
From e In dbACL.tbl_Physician.Where(Function(a) items.ID_Physician = a.ID_Physician).DefaultIfEmpty
Group Join f In dbACL.tbl_Title On e.ID_Title Equals f.ID_Title
Into Matched = Group
From m In Matched.DefaultIfEmpty()
Select
items.ID_Demog,
items.Last_Name,
items.First_Name,
AKALname = demogAKA.Last_Name,
AKAFname = demogAKA.First_Name,
items.DOB,
items.Gender,
items.ST_Complete,
items.LT_Complete,
items.LT_Due_Date,
DType.Disease_Type_Abr,
DGroup.Disease_Group_Name,
RFC.Reason_For_Call,
items.ID_Physician,
PLName = e.Last_Name,
PFname = e.First_Name,
PMI = e.Middle_Initial,
PTitle = m.Title
).Distinct.ToList().Select(Function(a) New With {
a.ID_Demog,
a.Last_Name,
a.First_Name,
.AKA_Name = (String.Join(", ",
String.Join(",",
New String() {a.AKALname, a.AKAFname}).Split(
New Char() {","}, System.StringSplitOptions.RemoveEmptyEntries))),
a.DOB,
a.Gender,
a.ST_Complete,
a.LT_Complete,
a.LT_Due_Date,
a.Disease_Type_Abr,
a.Disease_Group_Name,
a.Reason_For_Call,
a.ID_Physician,
.PName = (String.Join(", ",
String.Join(",",
New String() {a.PLName, a.PFname, a.PTitle}).Split(
New Char() {","}, System.StringSplitOptions.RemoveEmptyEntries)))
}).ToList()

Linq :Query Join And Equals( variable) Not Work

Dim ID_Section as Int32 = 10
Dim Query = From Book1 In db.Book1
Group Join Section In db.Section On CInt(Book1.ID_Section) Equals Section.ID_section _
And Section.ID_section Equals (ID_Section) Into Section_join = Group
From Section In Section_join.DefaultIfEmpty()
Select
Book1.ID_Book,
Book1.Name_Book,
ID_section = Section.ID_section,
Name_Section = Section.Name_Section
The error appears in the variable id_Section, since the Linq does not accept values ​​from the outside, as it seems to me of course.
Here Error :
And Section.ID_section Equals (ID_Section)
In SQL Query Use At :
Declare #ID_Section int
SELECT Book.ID_Book, Book.Name_Book, Section.ID_section, Section.Name_Section
FROM Book LEFT OUTER JOIN
Section ON Book.ID_Section = Section.ID_section and Section.ID_section = #ID_Section
where Book.ID_Book =1
Using Where on db.Section with lambda syntax:
Dim Query = From Book1 In db.Book1
Group Join Section In db.Section.Where(Function(s) s.ID_section = ID_Section)
On CInt(Book1.ID_Section) Equals Section.ID_section _
Into Section_join = Group
From Section In Section_join.DefaultIfEmpty()
Select
Book1.ID_Book,
Book1.Name_Book,
ID_section = Section.ID_section,
Name_Section = Section.Name_Section
Alternatively you can apply the Where to the Join results:
Dim Query = From Book1 In db.Book1
Group Join Section In db.Section
On CInt(Book1.ID_Section) Equals Section.ID_section _
Into Section_join = Group
From Section In Section_join.Where(Function(s) s.ID_section = ID_Section).DefaultIfEmpty()
Select
Book1.ID_Book,
Book1.Name_Book,
ID_section = Section.ID_section,
Name_Section = Section.Name_Section

Linq2Db / Access - error with anonymous group by multiple fields

I have 2 tables: pob (with results of some activities) and names (with user data). I try to select top 5 users from table pob based on their last activity date. So I inner join names and pob, then select top 5 users based on calculated max(date).
This query is working:
SELECT TOP 5
[u].[id],
[u].[name],
max([p].[date]) As LastDateOfUse,
FROM
[pob] [p]
INNER JOIN
[users] [u]
ON
[p].[id_name] = [u].[id]
WHERE
[p].[date] >= #2017-01-01#
GROUP BY
[u].[id],
[u].[name]
ORDER BY
max([p].[date]) DESC
Now I need to transform it into Linq query. This my attempt but it's not working.
"Key" is not a member of type "System.Collections.Generic.IEnumerable'1[VB$AnonymousType_2'2[pob,users]]".
Using db = New DbContext() With {.InlineParameters = True}
Dim query1 = From p In db.pob
Join u In db.users On p.id_name Equals u.id
Where p.date >= New Date(2017, 1, 1)
Group New With {p, u} By pu = New With {Key u.id, Key u.name} Into pg = Group
Select New RecentUser With
{
.id = pu.id,
.name = pu.name,
.LastDateOfUse = pg.Max(Function(f) f.p.date)
}
query1 = query1.OrderByDescending(Function(f) f.LastDateOfUse).Take(5)
Return query1.ToList
End Using
If I remove .LastDateOfUse = pg.Max(Function(f) f.p.Date) like below it works. By 'works' I mean there is no exception but of course result of query is wrong however grouping is done properly.
Using db = New DbContext() With {.InlineParameters = True}
Dim query1 = From p In db.pob
Join u In db.users On p.id_name Equals u.id
Where p.date >= New Date(2017, 1, 1)
Group New With {p, u} By pu = New With {Key u.id, Key u.name} Into pg = Group
Select New RecentUser With
{
.id = pu.id,
.name = pu.name
}
Return query1.ToList
End Using
Edit
I also tried going through navigation properties like below, but again I receive the same error.
Using db = New DbContext() With {.InlineParameters = True}
Dim query1 = From p In db.pob
Where p.date >= New Date(2017, 1, 1)
Group p By pu = New With {Key u.User.id, Key u.User.name} Into pg = Group
Select New RecentUser With
{
.id = pu.id,
.name = pu.name
.LastDateOfUse = pg.Max(Function(f) f.date)
}
query1 = query1.OrderByDescending(Function(f) f.LastDateOfUse).Take(5)
Return query1.ToList
End Using
And again if I remove .LastDateOfUse = pg.Max(Function(f) f.p.Date) like below it starts to work (proper grouping, wrong overall result).
Using db = New DbContext() With {.InlineParameters = True}
Dim query1 = From p In db.pob
Where p.date >= New Date(2017, 1, 1)
Group p By pu = New With {Key u.User.id, Key u.User.name} Into pg = Group
Select New RecentUser With
{
.id = pu.id,
.name = pu.name
}
Return query1.ToList
End Using
How can I transform above Sql query to Linq? (preferable answer in VB.Net but C# is ok too)
Solution
There is no solution yet. It looks like VB has bad Linq queries resolver - it creates unexpected method chain that can not be converted to SQL.
So instead
Group By ... Into pg = Group
we need
Group By ... Into LastDateOfUse = p.Max(Function(f) f.date).
See below full query.
Using db = New DbContext() With {.InlineParameters = True}
Dim query1 = From p In db.pob
Where p.date >= New Date(2017, 1, 1)
Group p By pu = New With {Key u.User.id, Key u.User.name} Into LastDateOfUse = p.Max(Function(f) f.date)
Select New RecentUser With
{
.id = pu.id,
.name = pu.name
.LastDateOfUse = LastDateOfUse
}
Return query1.ToList
End Using
Another problem with lambda syntax
Using lambda syntax we receive another exception.
Dim query = db.pob.
Where(Function(f) f.date >= New Date(2017, 1, 1).
GroupBy(Function(f) New With
{
Key .userid= f.user.id,
Key .username = f.user.name
}).Select(Function(f) New RecentUser With
{
.id = f.Key.userid,
.name = f.Key.username,
.LastDateOfUse = f.Max(Function(g) g.date)
}).ToList
Exception
VB.NET compiler adds unnecessary Convert to IEnumerable when generating Expression Tree.
An unhandled exception of type LinqToDB.Linq.LinqException occurred in linq2db.dll
Convert(f).Select(g => g.Date).Max() cannot be converted to SQL
GitHub
I posted an issue here.
Svyatoslav Danyliv based on my issue opened his own here.

Left Join Linq to Entity's Vb.net

I can't figure out that linq to entity query syntax. My problem is that if the value of the Calls table is null then noting comes up, I want to make something like a left join to get 'all' rows from the Calls table.
I tried to group it but I can't figure out the correct way to write it.
Dim TicketQuery As ObjectQuery = From c In EnData.Customer _
Join t In EnData.Calls On t.CustomerID Equals c.CustomerID _
Join Status In EnData.Lists On t.Status Equals Status.ListValue _
Join Project In EnData.Lists On t.Project Equals Project.ListValue _
Join Priorty In EnData.Lists On t.Priority Equals Priorty.ListValue _
Where c.Status > -1 And t.Status > -1 And Status.ListType = 1 And Project.ListType = 3 And Priorty.ListType = 2 _
Select New With {c.CustName, t.CallID, t.CallDate, t.CallTime, t.Description, Key .Status = Status.ListText, Key .Project = Project.ListText, t.DateModified, Key .Priority = Priorty.ListText}
How can I fix that?
Similar question: Linq to Sql: Multiple left outer joins
Microsoft Documentation: http://msdn.microsoft.com/en-us/library/bb918093.aspx#Y916
LINQ Examples from: http://msdn.microsoft.com/en-us/vbasic/bb737909
Left Outer Join
A so-called outer join can be expressed with a group join. A left outer joinis like a cross join, except that all the left hand side elements get included at least once, even if they don't match any right hand side elements. Note how Vegetables shows up in the output even though it has no matching products.
Public Sub Linq105()
Dim categories() = {"Beverages", "Condiments", "Vegetables", "Dairy Products", "Seafood"}
Dim productList = GetProductList()
Dim query = From c In categories _
Group Join p In productList On c Equals p.Category Into Group _
From p In Group.DefaultIfEmpty() _
Select Category = c, ProductName = If(p Is Nothing, "(No products)", p.ProductName)
For Each v In query
Console.WriteLine(v.ProductName + ": " + v.Category)
Next
End Sub
For left join in VB.net we can use Let
Dim q =
(From item In _userProfileRepository.Table
Let Country = (From p In _countryRepository.Table Where p.CountryId = item.CurrentLocationCountry Select p.Name).FirstOrDefault
Let State = (From p In _stateRepository.Table Where p.CountryId = item.CurrentLocationCountry Select p.Name).FirstOrDefault
Let City = (From p In _stateRepository.Table Where p.CountryId = item.CurrentLocationCountry Select p.Name).FirstOrDefault
Where item.UserId = item.ProfileId.ToString)
After the left join we can use group by
Dim q2 =
(From p In q Group p By p.Country, p.State, p.City Into Group
Select New With
{
.Country = Country,
.State = State,
.City = City,
.Count = Group.Count
}).ToList()