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

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()

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}

Why is this dropdown not populating?

I have three dropdowns; the second one (Members) populates based on what's in the first (Units), and the third one (Customers) SHOULD populate based on what's in the second; but it doesn't.
Here's the query that works in LINQPad (returns a list of Company names):
select distinct companyname from customers C left join members M on M.MemberNo = C.MemberNo where M.MemberNo = '052' order by companyname
...but does not work in the following code:
'Populate the Members dropdown
Dim selectedUnit As String = DropDownListUnits.Text
Dim membersDT As DataTable
sql = "select distinct shortname, M.memberno from members M left join memberunitproducts mup on M.MemberNo = mup.MemberNo where unit = '" + selectedUnit + "' order by shortname"
retDS = sqlDAL.runSQLDataSet(sql)
membersDT = retDS.Tables(0)
DropDownListMembers.DataSource = membersDT
DropDownListMembers.DataTextField = "shortname"
DropDownListMembers.DataValueField = "memberno"
DropDownListMembers.DataBind()
'Populate the Customers dropdown
Dim selectedMember As String = DropDownListMembers.DataValueField
Dim customersDT As DataTable
sql = "select distinct companyname from customers C left join members M on M.MemberNo = C.MemberNo where M.MemberNo = '" + selectedMember + "' order by companyname"
retDS = sqlDAL.runSQLDataSet(sql)
customersDT = retDS.Tables(0)
DropDownListCustomers.DataSource = customersDT
DropDownListCustomers.DataTextField = "companyname"
DropDownListCustomers.DataValueField = "companyname"
DropDownListCustomers.DataBind()
The third (Customers) dropdown remains unpopulated when this runs - why?
It's probably not needed, but here is the code for the first ("Units") dropdown:
'Populate the Units dropdown
Dim unitsDT As DataTable
sql = "Select distinct unit from masterunits where abs(active) = 1"
retDS = sqlDAL.runSQLDataSet(sql)
unitsDT = retDS.Tables(0)
DropDownListUnits.DataSource = unitsDT
DropDownListUnits.DataTextField = "unit"
DropDownListUnits.DataValueField = "unit"
DropDownListUnits.DataBind()
What am I missing or doing wrong?
I had to change this:
Dim selectedMember As String = DropDownListMembers.DataValueField
...to this:
Dim selectedMember As String = DropDownListMembers.SelectedItem.Value

"sum" of string separates with "," in linq to entities

I need a query of Linq to Entities with the result is a unic string separates with ",".
I try with Cont = String.Join(", ", op.OperacionesContenedores.Select(Function(jj) jj.Id.ToString).ToArray())
Example:
(From op In dContext.Operaciones
Join opTr In dContext.OperacionesTransito On opTr.idOperacion Equals op.id
Group Join opTrContenedor In dContext.OperacionesContenedores On opTr.idOperacion Equals opTrContenedor.IdOperacion Into grpOpTrContenedor = Group
From opTrContenedor In grpOpTrContenedor.DefaultIfEmpty()
Select New With {.idOperacion = op.id,
.Expediente = op.ReferenciaEmpresaResponsable,
.ReferenciaCliente = op.ReferenciaEmpresaCliente,
.Cont = String.Join(", ", op.OperacionesContenedores.Select(Function(jj) jj.Id.ToString).ToArray())
}).Distinct().AsQueryable
This should return "Expediente", "referenciaCliente", "con1, cont2, cont3", but in query execution produces a error:
LINQ to Entities does not recognize the method 'System.String Join(System.String, System.String[])' method, and this method cannot be translated into a store expression.

Linq query returning value from another class

Just a quick question about LINQ, I want to use a value from the returned dataset to lookup a value and return this. The line I am struggling with is .ViewingNotes = New Viewing(pt.ProspectId).GetViewings().Columns(7).ToString(). Is this possible?
With BusinessLayerObjectManager.Context
Return (From p As [Property] In .PropertySet
Join pt As Prospect In .Prospects On pt.Property.propertyID Equals p.propertyID
Where (p.Development.DevelopmentID = devId)
Select New DevelopmentList With {
.Apartment = p.propertyApartment + " " + p.Development.Name,
.PropertyId = p.propertyID,
.Client = pt.Client.clientFirstname + " " + pt.Client.clientLastname,
.ClientId = pt.Client.ClientID,
.ProspectiveDate = pt.prospectiveDate,
.ProspectiveStatus = pt.prospectiveStatus,
.Agent = pt.Client.userID,
.ViewingNotes = New Viewing(pt.ProspectId).GetViewings().Columns(7).ToString(),
.PropertyStatus = ""
}).ToList()
End With
Thanks in advance.
I suspect not, when the compiler tries to convert the 'Viewing(pt.ProspectId).GetViewings().Columns(7).ToString()' bit to SQL it will get very confused. You would need to do it in 2 stages.
Do the first Linq-to-entity select first returning just pt.ProspectId, with the .ToList() intact. Then use the results to do some more linq, linq-to-linq if you like, where you can do the lookup using a lambda.

Linq syntax in VB.NET

What I really want is to select these two tables in to an anon type like in Scott Gu's blog: here However, I would settle for this created type "ActiveLots" I am joining two tables together and want to be able to reference columns from each in my result set.
I don't seem to be getting the syntax correctly.
Dim pi = From p In dc.Inventories Join i In dc.InventoryItems On p.InventoryItemID _
Equals i.InventoryItemID Where p.LotNumber <> "" _
Select New ActiveLots LotNumber = p.LotNumber, Quantity = p.Quantity, Item = i.Item, Uom = i.UnitofMeasure, Description = i.Description
Have a look at Daniel Moth's blog entry. I suspect you want:
Dim pi = From p In dc.Inventories _
Join i In dc.InventoryItems
On p.InventoryItemID Equals i.InventoryItemID _
Where p.LotNumber <> "" _
Select New With { .LotNumber = p.LotNumber, .Quantity = p.Quantity, _
.Item = i.Item, .Uom = i.UnitofMeasure, _
.Description = i.Description }
That's using an anonymous type - to use a concrete type, you'd use New ActiveLots With { ... (where ActiveLots has to have a parameterless constructor).