Using Results Outside of dbContext in VB.NET - asp.net

Lets say I have code like this:
Using dbContext as mydb_entities = New mydb_entities
Dim qperson = (From p in dbContext.People _
Where p.name = "John" _
Select p)
End Using
Using dbContext as yourdb_entities = New yourdb_entities
Dim qyou = (From p in dbContext.Customer _
Where p.name = "John" _
Select p)
End Using
How can I compare the results of qperson to qyou? Since the results "disappear" once End Using is executed?

You will need to declare both variables outside the using statements
Dim qperson As IQueryable(Of Person)
Dim qyou As IQueryable(Of Customer)
Using dbContext as mydb_entities = New mydb_entities
qperson = (From p in dbContext.People _
Where p.name = "John" _
Select p)
End Using
Using dbContext as yourdb_entities = New yourdb_entities
qyou = (From p in dbContext.Customer _
Where p.name = "John" _
Select p)
End Using

The key thing here is to know when your LINQ queries are going to get run. When this line of code is executed:
qperson = (From p in dbContext.People _
Where p.name = "John" _
Select p)
no query is sent to the server. Instead you get back an object that implements the IQueryable(Of T) interface that describes what the query is. The query isn't actually sent to the server and executed until you start to use the results, such as in a For Each loop. This is called delayed execution and is fundamental to LINQ.
So what does this mean to you? Well, it means that the context must not be disposed before you execute the query. In the examples so far this is not necessarily always true. (The nested answer might do so, depending what is actually happening inside the nested usings.)
The typical way to deal with this is to force execution of the query to produce an in-memory collection of the results before the context is disposed. The ToList() extension method is a common way to do this. So, for example:
Dim qperson As IList(Of Person)
Dim qyou As IList(Of Customer)
Using dbContext as mydb_entities = New mydb_entities
qperson = (From p in dbContext.People _
Where p.name = "John" _
Select p).ToList()
End Using
Using dbContext as yourdb_entities = New yourdb_entities
qyou = (From p in dbContext.Customer _
Where p.name = "John" _
Select p).ToList()
End Using
Now you have executed the queries and got the results into memory before the contexts are disposed and you can happily do what you want with them.

In C# I just typically nest the usings...
using (var context blahentities())
{
using (var context2 blahentities())
{
}
}
Check this out for nesting usings in vb...
Nested using statements

Related

Multiple rollbacks to diferents methods Entity Framework VB.NET

I have this case: I'm using Entity Framework 6 and I'm trying to insert registries into multiple tables like this:
public function method1(model as modelType) as result
dim answer = new result(false)
dim ent = new entitiType
Using context as database1Context
Using dbContextTransaction = context.Database.BeginTransaction()
Try
model.property3 = method2(model.property2, model.property2)
model.property4 = method3()
context.ent.add(model)
contex.savechanges()
answer.ok = true
DbContextTransaction.Commit()
catch ex as Exception
answer.detail = ex.innerException.Message
DbContextTransaction.Rollback()
End Try
End Using
End Using
Return Answer
End Function
This is my method2:
public function method2(idcta as integer) as integer
dim folio As integer
using context as new database1Context
dim values = (From a In context.cuentas
Where a.idcta = idcta Select a).SingleOrDefault()
values.property = values.property +1
context.savechanges()
End Function
(The method3 is identical to method2)
My question is, how can I roll back every insert or update, when any one of these methods fails?
Thank you for your help.

LINQ to Entities does not recognize the method - simple delete statement

I have a GridView and on a row being deleted I trigger the GridView1_RowDeleting sub, but I receive an error "LINQ to Entities does not recognize the method 'System.Web.UI.WebControls.TableCell get_Item(Int32)' method, and this method cannot be translated into a store expression." Code is:
Private Sub GridView1_RowDeleting(sender As Object, e As System.Web.UI.WebControls.GridViewDeleteEventArgs) Handles GridView1.RowDeleting
' The deletion of the individual row is automatically handled by the GridView.
Dim dbDelete As New pbu_housingEntities
' Remove individual from the bed.
Dim remove_bed = From p In dbDelete.Beds _
Where p.occupant = GridView1.Rows(e.RowIndex).Cells(3).Text _
Where p.room = GridView1.Rows(e.RowIndex).Cells(6).Text _
Where p.building = GridView1.Rows(e.RowIndex).Cells(5).Text _
Order By p.id Descending _
Select p
remove_bed.First.occupant = ""
dbDelete.SaveChanges()
' Increase number of open spaces in room.
Dim update_occupancy = From p In dbDelete.Rooms _
Where p.room1 = GridView1.Rows(e.RowIndex).Cells(6).Text
Where p.building = GridView1.Rows(e.RowIndex).Cells(5).Text _
Select p
update_occupancy.First.current_occupancy = update_occupancy.First.current_occupancy - 1
dbDelete.SaveChanges()
End Sub
The specific line erroring out is:
remove_bed.First.occupant = ""
That's because the Linq query is translated to SQL, and there is no way to translate GridView1.Rows(e.RowIndex).Cells(3).Text to SQL. You need to extract the values from the GridView, then use them in the query
Dim occupant As String = GridView1.Rows(e.RowIndex).Cells(3).Text
Dim room As String = GridView1.Rows(e.RowIndex).Cells(6).Text
Dim building As String = GridView1.Rows(e.RowIndex).Cells(5).Text
Dim remove_bed = From p In dbDelete.Beds _
Where p.occupant = occupant _
Where p.room = room _
Where p.building = building _
Order By p.id Descending _
Select p
You have to put those values in variables before executing the query, otherwise the Entity Provider will try to pull the whole object into the query and access it's properties when it is trying to translate it into a SQL statement - which fails since there is no SQL equivalent.

Variable is Not Available Using dbContexts?

I have the following code that throws an error on the call for getOccupants within Using dbPC. Is there a way to make this value accessible in Using dbPC? Or a better way to accomplish this? I tried nesting Using dbPC inside of Using dbContext, but that also throws errors.
Protected Sub btnPushSemester_Click(sender As Object, e As EventArgs) Handles btnPushSemester.Click
Dim dbPC As New Campus6Entities
Dim dbContext As New pbu_housingEntities
Using dbContext
Dim get_Year = From p In dbContext.Configs _
Where p.Description = "year" _
Select p
Dim get_Term = From p In dbContext.Configs _
Where p.Description = "term" _
Select p
Dim thisYear = get_Year.First.textValue
Dim thisTerm = get_Term.First.textValue
Dim getOccupants = From p In dbContext.Residents _
Where p.semester = thisTerm _
Where p.year = thisYear _
Select p
End Using
Using dbPC
For Each row In getOccupants
Dim student_info = row
Dim PCstudent = From r In dbPC.RESIDENCies _
Where student_info.people_code_id = r.PEOPLE_ID _
Where r.ACADEMIC_YEAR = thisYear _
Where r.ACADEMIC_TERM = thisTerm _
Select r
For Each row2 In PCstudent
Dim student_info2 = row2
student_info2.DORM_BUILDING = student_info.Building1.building_code
student_info2.DORM_ROOM = student_info.Room1.room1
student_info2.RESIDENT_COMMUTER = "R"
student_info2.DORM_CAMPUS = "O000000001"
dbPC.SaveChanges()
Next
Next
End Using
End Sub
Because of deferred execution, the query is not actually executed until you iterate over the collection, but by that time, you have already disposed the dbContext object. Try enclosing all the code in the Using blocks:
Using dbPC As New Campus6Entities
Using dbContext As New pbu_housingEntities
'rest of code here
End Using
End Using
Declare getOccupants outside of the using block. You can assign it inside the block, but you have to declare it outside or it goes out of scope before you actually need to use it.

LINQ Query based on user preferences

How can I do this better (so it actually works : )
I have a LINQ Query that includes an order by that is based on a user preference. The user can decide if they would like the results ordered asc or desc.
If fuserclass.SortOrder = "Ascending" Then
Dim mydat = (From c In dc.ITRS Order By c.Date Ascending Select c)
Else
Dim mydat = (From c In dc.ITRS Order By c.Date Descending Select c)
End If
For each mydata in mydat ***<<<error "mydat is not declared"***
I know I could put my For Each loop inside the If and Else, but that seems silly to have the same code twice. I know you know of a better way : )
Define Dim mydat before the If statement. It is out of scope when your code reaches the for loop.
Use an extension method, and function-based LINQ (my VB is rusty as hell)
VB.NET:
<ExtensionAttribute> _
Public Shared Function OrderByString(Of TSource, TKey) ( _
source As IEnumerable(Of TSource), _
keySelector As Func(Of TSource, TKey) _
strType As String) As IOrderedEnumerable(Of TSource)
If strType = "Ascending" Then
return source.OrderBy(keySelector)
Else
return source.OrderByDescending(keySelector)
End If
End Function
C#.NET:
public static IOrderedEnumerable<TSource> OrderByString(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
string strType)
{
if (strType == "Ascending")
return source.OrderBy(keySelector);
return source.OrderByDescending(keySelector);
}
Then call your query like this:
Dim mydat = dc.ITRS.OrderByString(Function(item) item.Date, fuserclass.SortOrder)
Or on C#:
var mydat = dv.ITRS.OrderbyString(item => item.Date, fuserclass.SortOrder);

Is there a better way to do this LINQ statement block?

I'm relatively new with LINQ, but I'm going to be getting into it a lot more. Is the following a practical application of LINQ, or is there a better way to do something like this?
Public Shared Function GetItems(ByVal itemsList As List(Of OrderItem),
ByVal whichForm As ItemsFor, ByVal formID As Integer) As List(Of OrderItem)
Dim items As New List(Of OrderItem)
Select Case whichForm
Case ItemsFor.MfrCredit
Dim query = From oi As OrderItem In itemsList _
Where oi.ManufacturerCreditID = formID Select oi
items = query
Case ItemsFor.CustomerCredit
Dim query = From oi As OrderItem In itemsList _
Where oi.CustomerCreditID = formID Select oi
items = query
Case ItemsFor.Invoice
Dim query = From oi As OrderItem In itemsList _
Where oi.InvoiceID = formID Select oi
items = query
Case ItemsFor.PurchaseOrder
Dim query = From oi As OrderItem In itemsList _
Where oi.PurchaseOrderID = formID Select oi
items = query
Case ItemsFor.Quote
Dim query = From oi As OrderItem In itemsList _
Where oi.QuoteID = formID Select oi
items = query
Case ItemsFor.StockingOrder
Dim query = From oi As OrderItem In itemsList _
Where oi.StockingOrderID = formID Select oi
items = query
End Select
Return items
End Function
I was thinking if I could get the property name somehow as an object I could just do one LINQ statement, but I'm not sure exactly how...
Thanks!
You could do something like:
Dim condition As Func(Of OrderItem, Boolean)
Select Case whichForm
Case ItemsFor.MfrCredit
condition = Function(oi As OrderItem) oi.ManufacturerCreditID = formID
Case ItemsFor.CustomerCredit
condition = Function(oi as OrderItem) oi.CustomerCreditID = formID
...
End Select
Return items.Where(condition).ToList()
It's not perfect but at least it's less code duplication...
You could use the Predicate delegate. I am imagining an array or list of Predicates, one for each ItemsFor.
Then your query is
Dim query = From oi As OrderItem In itemsList Where predicate select oi
See also this article on building predicates.
And this article on PredicateBuilder.
You can use LINQ expressions, like this: (My VB is rusty, so this might not compile)
Dim param = Expression.Parameter(GetType(OrderItem), "item")
Dim getter = Expression.Lambda(Of Func(Of OrderItem, Integer))( _
Expression.Property(param, whichForm.ToString()), _
param _
).Compile()
Return items.Where(Function(item) getter(item) == formId)
For optimal performance, cache the generated delegates in a Dictionary(Of ItemsFor, Func(Of OrderItem, Integer)).
EDIT:
The System.Linq.Expressions namespace allows you to create functions at runtime. This coe uses the feature to create a function that gets a property. Since the Compile method (which actually creates the function) is somewhat slow, it's better to reuse each generated delegate.
one way that you could turn that into a single query would be something like:
Dim query = From oi As OrderItem In itemsList _
Where ((whichForm = ItemsFor.MfrCredit) and (oi.ManufacturerCreditID = formID)) _
or ((whichForm = ItemsFor.CustomerCredit) and (oi.CustomerCreditID = formID)) _
or ((whichForm = ItemsFor.Invoice) and (oi.InvoiceID = formID)) _
...
select oi
items = query
There is a nice article here on Dynamic searchs in Linq with VB by creating an expression tree manually...quite a bit of work. Or another (better?) option is this article on Dynamic Linq (examples here are in C#)

Resources