Compiled LINQ joins - vb.net

I have got a bit of slack project time so I decided to star in a production of micro optimisation theatre. The app I’m working on is going to be run on some quite low performance tablets so I was looking for ways to speed things up. Given that I’m using LINQ to Entities I looked into precompiled queries to boast performance and so came up with this simple one to return a list of contacts for a given company
Public ReadOnly pContacts_list_query As Func(Of SpiraxDDWEntities, Integer, IQueryable(Of tblContacts)) = _
CompiledQuery.Compile(Of SpiraxDDWEntities, Integer, IQueryable(Of tblContacts))(Function(ctx As SpiraxDDWEntities, pCompany_ABN As Integer) _
From Contact_data In ctx.tblContacts Where Contact_data.AccountNumber = pCompany_ABN
)
Now that’s fine as its just one table so the IQueryable type can be the table name. My question is what if I wanted to precompile a query with joins? For example this one
Dim Quote_QRY = From Quote_data In linEntities.tblQuote
Join Quote_value_data In linEntities.tblQuoteValue On Quote_data.ID Equals Quote_value_data.QuoteID
Join Quote_status_data In linEntities.tblQuoteStatus On Quote_data.Status Equals Quote_status_data.Abbreviation
Where Quote_data.AccountNo = Me.txtCompany_ABN.Text
Select Quote_data.ID, Status = Quote_status_data.Description, Quote_data.Contact, Quote_data.Project, Quote_value_data.QuoteValue
How would I go about that?
Thanks

The join here isn't the issue. The issue is projecting into an anonymous type. In this case, you need to create a named class and project into it in your select clause:
Public Class Quote
Public Property ID As String
Public Property Status As String
Public Property Contact As String
Public Property Project As String
Public Property QuoteValue As String
End Class
Public ReadOnly Quote_query As Func(Of SpiraxDDWEntities, Integer, IQueryable(Of Quotes)) = _
CompiledQuery.Compile(Of SpiraxDDWEntities, Integer, IQueryable(Of Quotes))(Function(ctx As SpiraxDDWEntities, pCompany_ABN As Integer)
From Quote_data In linEntities.tblQuote
Join Quote_value_data In linEntities.tblQuoteValue On Quote_data.ID Equals Quote_value_data.QuoteID
Join Quote_status_data In linEntities.tblQuoteStatus On Quote_data.Status Equals Quote_status_data.Abbreviation
Where Quote_data.AccountNo = Me.txtCompany_ABN.Text
Select New Quote With {
.ID = Quote_data.ID,
.Status = Quote_status_data.Description,
.Contact = Quote_data.Contact,
.Project = Quote_data.Project,
.QuoteValue = Quote_value_data.QuoteValue
}
One additional caveat: I'm not sure off the top of my head if Compiled Query requires that the resulting type be a mapped entity type or not. If so, you may need to alter the EDMX to make EF think that the type is valid. Alternatively, you may want to consider using a Defining query in the CSDL.

Related

Problems using linq in order to find differences in datatables

I have the following function:
Public Function Check_Desparity(Byval dtTestStep as DataTable, Byval dtLimits as DataTable) as DataTable
Dim diff = dtTestSteps.AsEnumerable.Union(dtLimits.AsEnumberable).Except(dtTestSteps.Intersect(dtLimits.AsEnumerable))
End Function
I expect, that diff contains the rows with differences. But it doesn`t. I have two differences, but diff contains only one and that one is no difference.
When I try the same thing with List(Of String) instead of DataTable it works perfect.
Public Function Check_Desparity(Byval TestStep as List(Of String), Byval Limits as List(Of String)) as List(Of String)
Dim diff = TestStep.Union(Limits).Except(TestStep.Intersect(Limits))
End Function
Here I get exactly the two differences of both lists back in diff.
Could somebody explain me why?
Thank you
EDIT:
With help of you, I got exactly what I wanted. The function for my answer is the following:
Public Function Check_Desparity(Byval dtTestStep as DataTable, Byval dtLimits as DataTable) as IEnumerable(Of DataRow)
Dim diff = dtLimits.AsEnumerable.Except(dtTestSteps.AsEnumberable, DataRowComparer.Default)
Return diff
End Function
But I forgot to mention an important detail.
This function works only if both of the tables have the same columns.
In my case, the columns are different, but column "dictkey". Column "dictkey" exists in both of my datatables.
How I get it to work, that my function returns only rows, where "dictkey" is different respectivly not existent?
You can't use Except, Intersect or Union in this way because DataRow.Equals is not overridden, hence it will just compare references and all are different. You can use DataRowComparer.Default which compares all columns of the row with all columns of the other row.
Your LINQ query doesn't make sense either, i guess you want something like this:
Public Function Check_Desparity(ByVal dtTestStep As DataTable, ByVal dtLimits As DataTable) As DataTable
Dim stepRows = dtTestStep.AsEnumerable()
Dim limitRows = dtLimits.AsEnumerable()
Dim allInStepButNotInLimit = stepRows.Except(limitRows, DataRowComparer.Default)
Dim allInLimitButNotInStep = limitRows.Except(stepRows, DataRowComparer.Default)
End Function
I think this is because DataTable.AsEnumerable returns IEnumerable of DataRow.
DataRow is a reference type and since LINQ uses Equals() for comparison to find differences, all rows from both tables are considered to be different (they all are different objects).
Your code works for strings because they are compared using their content, like value types.

How to return a List(Of String) from a LINQ statement with a Group By in VB.net?

I've seen several questions on how to do this in C# but I'm having trouble translating those to VB. Here's the basics of my issue:
Table of data NOT normalized and accessed via Entity Framework
Get all unique string values in a certain field
Convert those values to a List(Of String)
This works but I'm guessing there's a better way to do it without iterating through the list:
Public Function GetGroups() As IEnumerable(Of String)
Dim GroupList As New List(Of String)
Dim CodeList = (From c In Context.Codes
Group c.Group By c.Group Into g = Group)
For Each c In CodeList
GroupList.Add(c.Group)
Next
Return GroupList
End Function
What I seem to be struggling with the most is using Group By in LINQ. I'm guessing this could probably be done in 1 or 2 lines by having LINQ return just the list of strings or by converting the list of anonymous objects to a list of strings.
Well, if you don't need anything in the group, you can just use .Distinct():
Return (
From c In Context.Codes
Order By c.Group
Select c.Group
).Distinct().ToList()
Edit: Added Order By

Using db find on something other than primary key

I have searched on the internet for the answer to this question with no luck. I have a model that exists in the database created with entity framework and is accessed via db (an instance of my DBContext class).
Function Details(id As Integer) As ViewResult
Dim bill As Bill = db.Bills.Find(id)
Return View(bill)
End Function
The model for the bill class is defined as:
Public Class Bill
Public Property BillId() As Integer
Public Property CustomerId() As String
Public Property BillStatus() As String
End Class
Assuming then that in the function mentioned first I was passed the CustomerId rather than the primary key of this model (BillId), how would I use this to create a 'Dim bill As Bill' that included only bills with that customerId.
As in the example above where Bills was filtered with .Find(id) - which only looks at the primary key - I want to be able to use a similar method to .Find() but on a non key field: CustomerId in this case.
Or if you are sure there will always only be one entity matching that CustomerId you can use Single.
VB .NET
Dim myBill = db.Bills.Single(Function(x) x.CustomerId = cId);
C# .NET
var myBill = db.Bills.Single(x => x.CustomerId == cId);
Note, this will throw an exception if exactly 1 is not found.
Use .NET's version of filter (Where)
Dim bills = db.Bills.Where(Function(x) x.CustomerId = cId)
or
Dim bills = From i in db.Bills
Where i.CustomerId = cId
Select i
EDIT: Per Scott's comment call FirstOrDefault() to return only one result. If none exist, null will be returned.
To do that.
Dim bill = (From i in db.Bills
Where i.CustomerId = cId
Select i).FirstOrDefault()
If Not IsDBNull(bill) Then
/*Do Stuff*/
End If

LINQ VB.NET variable not found when looping through grouped query

I'm trying to do the following LINQ grouping, which works in the debugger (the results are populated in the GroupedOrders object. But VS 2008 gives me the following error at design time...
Name 'x' is not declared
Dim GroupedOrders = (From m In thisConsultant.orders _
Group m By Key = m.commCode Into Group _
Select commCode = Key, orders = Group)
For Each x In GroupedOrders
Next
Public Structure consultantDetail
Public orders As List(Of orderDetail)
End Structure
Public Structure orderDetail
Public transactionID As Integer
Public qualifyingVolume As Decimal
Public commissionableVolume As Decimal
Public sponsorID As Integer
Public orderDate As DateTime
Public commCode As String
Public commPercentage As Decimal
Public discountPercent As Decimal
End Structure
Do you have Option Infer On?
My guess is that you have Option Strict On and Option Infer Off. To check these settings:
Right-click on your project in the solution explorer
Select Properties
Select the Compile tab on the left
try to surround the Linq query with try-catch. Sometimes, there are error that are not catch directly by VS2008.

How to add custom columns to a table that LINQ to SQL can translate to SQL

I have a table that contains procedure codes among other data (let's call it "MyData"). I have another table that contains valid procedure codes, their descriptions, and the dates on which those codes are valid. Every time I want to report on MyData and include the procedure description, I have to do a lookup similar to this:
From m in dc.MyDatas _
Join p in dc.Procedures On m.proc_code Equals p.proc_code _
Where p.start_date <= m.event_date _
And If(p.end_date.HasValue, p.end_date.Value, Now) >= m.event_date _
Select m.proc_code, p.proc_desc
Since there are many places where I want to show the procedure description, this gets messy. I'd like to have the lookup defined in one place, so I tried putting this in an extension of MyData:
Partial Public Class MyData
Public ReadOnly Property ProcedureDescription() As String
Get
Dim dc As New MyDataContext
Return _
(From p in dc.Procedures _
Where p.proc_code = Me.proc_code _
And p.start_date <= Me.event_date _
And If(p.end_date.HasValue, p.end_date.Value, Now) >= Me.event_date _
Select p.proc_desc).SingleOrDefault
End Get
End Property
End Class
Which works when displaying data, but you can't use it in a query, because it doesn't know how to turn it into a SQL statement:
Dim test = _
From x In dc.MyDatas _
Select x.proc_code _
Where x.ProcedureDescription.Contains("test")
Error: The member 'MyProject.MyData.ProcedureDescription' has no supported translation to SQL.
Is there a way to turn a complex lookup (i.e. a non-trivial join) like this into something SQL can recognize so that I can define it in one place and just reference the description as if it were a field in MyData? So far the only thing I can think of is to create a SQL view on MyData that does the linking and bring that into my data context, but I'd like to try to avoid that. Any ideas would be welcomed. Thanks.
You can insert an AsEnumerable() into your query expression. That will convert everything to that point to a result set so that your custom properties can be included in the remainder of the expression (sorry I don't do VB):
var test = _
(from x in dc.MyDatas
(select x.proc_code).AsEnumerable().
Where(x => x.ProcedureDescription.Contains("test"));
this is not an extension method, it's a property
VB.net Extension methods: http://msdn.microsoft.com/en-us/library/bb384936.aspx
Consider using this query as a stored procedure if it's really important.