Linq2Db / Access - error with anonymous group by multiple fields - vb.net

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.

Related

LINQ to DATASET with Group Join and Group By (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}

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 Out of memory exception

when i execute this code
Dim lstOrders = (From oRR In dbO.orders
Where oRR.datum = werkdatum And oRR.status >= 40).ToList
Dim lstOrderRegels = (From ord In lstOrders
Join ordreg In dbO.orderregels On ord.ID Equals ordreg.ordernummer
Group Join a In dbO.artikelen On ordreg.artikelID Equals a.ID Into resultArt = Group
From art In resultArt.DefaultIfEmpty(New artikelen With {.ID = 0, .subgroep = 0})
Group Join subgr In dbO.productsubgroep On art.subgroep Equals subgr.ID Into result = Group
From sgr In result.DefaultIfEmpty(New productsubgroep With {.ID = 0, .productgroep_ID = 0})
Group Join hgr In dbO.productgroep On sgr.productgroep_ID Equals hgr.ID Into result2 = Group
From hg In result2.DefaultIfEmpty(New productgroep With {.ID = 0})
Join ses In dbO.sessie On ord.sessieID Equals ses.ID
Order By hg.code, sgr.code).ToList
i get a system out of memory execption.. First i though it could be the null value in one of the columns but even changing it to 0 it did not help. That second query should return around 4k result.

mscorlib error on LINQ

I am getting an InvalidOperation on mscorlib.dll error on the following query and cannot for the life of me figure out why.
Here is the Class I'm populating the List(Of ) with;
Public Class ProjectionPerformance
Public SymbolId As Long
Public Name As String
Public ProjectionDate As String
Public ActualRange As Double
Public ProjectedRange As Double
End Class
Those types match the types in the table except for the date which I convert to a string
Here is the function with the LINQ query
Public Shared Function GetRangeProjectionPerformance(Optional daysToRetrieve As Integer = 100) As Dictionary(Of Long, List(Of ProjectionPerformance))
Dim todaysDate As Date = DateTime.Now.Date
Dim lookbackDate As Date = todaysDate.AddDays(daysToRetrieve * -1)
Using ctx As New ProjectionsEntities
Dim query = (From d In ctx.projections
Where d.SymbolId <= 42 AndAlso d.Date >= lookbackDate
Join t In ctx.symbols On d.SymbolId Equals t.Id
Let actualRange = d.ActualHigh - d.ActualLow
Let projectedRange = d.HighProjection - d.LowProjection
Select New With {
d.Date,
d.SymbolId,
t.Name,
projectedRange,
actualRange}).GroupBy(Function(o) o.SymbolId).ToDictionary(Function(p) p.Key,
Function(x) x.Select(Function(y) New ProjectionPerformance() With {
.SymbolId = y.SymbolId,
.ProjectionDate = y.Date.ToString(),
.Name = y.Name,
.ActualRange = y.actualRange,
.ProjectedRange = y.projectedRange
}).ToList())
Return query
End Using
End Function
I'm getting that error on this part of the LINQ query (Im assuming that is that portion is highlighted in green in VS2013)
Function(x) x.Select(Function(y) New ProjectionPerformance() With {
Do I have to retrieve the actual field values and eliminate the Let statements and do the calcs in the List function of the Dictionary call?
Based on your comments, I suspect that the problem is that you try to assign null values to the double properties in ProjectionPerformance. It should work if you change your code as follows:
Dim query = (From d In ctx.projections
Where d.SymbolId <= 42 AndAlso d.Date >= lookbackDate
Join t In ctx.symbols On d.SymbolId Equals t.Id
Let actualRange = d.ActualHigh - d.ActualLow
Let projectedRange = d.HighProjection - d.LowProjection
Select New With {
d.Date,
d.SymbolId,
t.Name,
projectedRange,
actualRange}).GroupBy(Function(o) o.SymbolId).ToDictionary(Function(p) p.Key,
Function(x) x.Select(Function(y) New ProjectionPerformance() With {
.SymbolId = y.SymbolId,
.ProjectionDate = y.Date.ToString(),
.Name = y.Name,
.ActualRange = If(y.actualRange, 0.0),
.ProjectedRange = y.projectedRange
}).ToList())
In order to spot such errors before running the program, you should set OPTION STRICT to ON (either on project or file level).

Converting SQL Query to LINQ 2 SQL expression

How can i rewrite the below SQL query to its equivalent LINQ 2 SQL expression (both in C# and VB.NET)
SELECT t1.itemnmbr, t1.locncode,t1.bin,t2.Total
FROM IV00200 t1 (NOLOCK)
INNER JOIN
IV00112 t2 (NOLOCK)
ON t1.itemnmbr = t2.itemnmbr
AND t1.bin = t2.bin
AND t1.bin = 'MU7I336A80'
EDIT: I noticed you asked for both C# and VB.Net, so I added a VB example.
C#
using (var txn = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadUncommitted
}
))
{
var results = from t1 in db.IV00200
join t2 in db.IV00112
on new { t1.itemnmbr, t1.bin }
equals new { t2.itemnmbr, t2.bin }
where t1.bin == 'MU7I336A80'
select new {t1.itemnmbr, t1.locncode, t1.bin, t2.Total};
// Do something with your results
}
VB.Net
Dim results
Dim transOptions = New TransactionOptions
transOptions.IsolationLevel = IsolationLevel.ReadUncommitted
Using txn As New TransactionScope(TransactionScopeOption.Required, transOptions)
results = From t1 In db.IV00200 _
Join t2 In db.IV00112 _
On New With {.itemnmbr = t1.itemnmbr, .bin = t1.bin} _
Equals New With {.itemnmbr = t2.itemnmbr, .bin = t2.bin} _
Where t1.bin = "MU7I336A80" _
Select New With {.itemnmbr = t1.itemnmbr, .locncode = t1.locncode, .bin = t1.bin, .Total = t2.Total}
' Do something with your results
End Using
You will need to add System.Transactions to your project's references.
The TransactionScope block recreates the nolock conditions in your original query.
[TransactionScope / NoLock Source: http://madprops.org/blog/linq-to-sql-and-nolock-hints/]