Select query in LINQ based on Foreign Table - asp.net

I have 2 Tables , OrderDetails and Requests In my LINQ to SQL dbml file.
OrderDetailsID is a foreign key in Requests Table.
I want to write the query to get the sum of UnitCost from OrderDetails based on OrderId.
And If there is a row in Requests Table for each OrderDetailsID, and the Requests.Last.RequestType="Refund" I want to reduce the total refund amount from the main sum otherwise If there is no row based on OrderDetailsID, add to sum.
Here is the way I implement that.
I am looking to prevent using "For each".
Any Solutions?
iRefund = (From od1 In dc.OrderDetails _
Where od1.OrderID =1 _
Select od1.UnitCost).Sum
Dim objOrderDetails = (From od1 In dc.OrderDetails _
Where od1.OrderID =1 _
Select od1)
For Each OrderDetail As ORM.Entities.OrderDetail In objOrderDetails
If Not OrderDetail.Requests Is Nothing Then
IF OrderDetail.Requests.Last.RequestType="Refund" Then
iRefund -= OrderDetail.UnitCost
End If
End If
Next

If you want to use Linq instead of a for-each loop, you could use the ForEach extension method of List:
Dim objOrderDetails = (From od1 In dc.OrderDetails _
Where od1.OrderId = 1 _
Select od1).ToList()
objOrderDetails.ForEach(AddressOf DecrementAmount)
Private Sub DecrementAmount(ByVal d As OrderDetail)
If d.Requests IsNot Nothing _
AndAlso d.Requests.Count > 0 _
AndAlso d.Requests.Last.RequestType = "Refund" Then
iRefund -= d.UnitCost
End If
End Sub
This assumes that iRefund is a module variable.

Related

How can I compare a text box entry against a list of database values in the Text_Changed event

as the title states I am trying to compare or validate a text box entry against a list of acceptable values stored in my database. As of now I have taken the values from my database and store them in a List(of String) and I have a for loop that loops through that list and returns true if the values match, if the values do not match it will return false. Below I have attached the code I am currently working with.
Protected Sub txtSearchOC_TextChanged(sender As Object, e As EventArgs) Handles txtSearchOC.TextChanged
Dim listEType As List(Of String) = New List(Of String)
Dim eType As String = txtSearchOC.Text
Dim strResult As String = ""
lblPrefix.Text = ""
lblList.Text = ""
Dim TypeIDQuery As String = "
SELECT a.OrderCode
FROM SKU AS a
INNER JOIN EnrollmentType AS e ON a.EnrollmentTypeID = e.TypeID
INNER JOIN Enrollment AS f ON e.RecID = f.EnrollmentTypeID
WHERE f.AccountNumber = '12345';
"
Using connEType As New SqlConnection(ConfigurationManager.ConnectionStrings("WarrantyConnectionString").ToString)
Using cmdEType As New SqlCommand(TypeIDQuery, connEType)
cmdEType.Parameters.Add("#AccountNumber", SqlDbType.VarChar, 15).Value = "12345"
connEType.Open()
Using sdrEType As SqlDataReader = cmdEType.ExecuteReader
While sdrEType.Read
listEType.Add(sdrEType("OrderCode").ToString)
End While
End Using
End Using
End Using
For Each Item As String In listEType
strResult &= Item & ", "
Next
For i = 0 To listEType.Count - 1
If eType = listEType(i) Then
lblPrefix.Text = "True"
End If
If eType <> listEType(i) Then
lblList.Text = "Error"
End If
Next
'lblList.Text = strResult
End Sub
In the code I declare my list and a variable to store the text value of the text box. To verify that it pulled the appropriate values from the database I have the strResult variable and can confirm that the appropriate values are being stored.
The problem I am having has to do with the For loop I have at the bottom, when I enter in a valid value that is contained in the listEType, I get the confirmation message of "True" indicating it has matched with one of the values, but I also get the "Error" message indicating that it does not match. If I enter in a value that is not contained in the list I only get the "Error" message which is supposed to happen.
My question is, based on the code I have supplied, why would that For loop be returning both "True" and "Error" at the same time for a valid entry? Also, if there is a better way to accomplish what I am trying to do, I am all ears so to speak as I am relatively new to programming.
Well, as others suggested, a drop down (combo box) would be better.
However, lets assume for some reason you don't want a combo box.
I would not loop the data. You have this amazing database engine, and it can do all the work - and no need to loop the data for such a operation. Why not query the database, and check for the value?
Say like this:
Protected Sub txtSearchOC_TextChanged(sender As Object, e As EventArgs) Handles txtSearchOC.TextChanged
If txtSearchOC.Text <> "" Then
Dim TypeIDQuery As String = "
SELECT a.OrderCode FROM SKU AS a
INNER JOIN EnrollmentType AS e ON a.EnrollmentTypeID = e.TypeID
INNER JOIN Enrollment AS f ON e.RecID = f.EnrollmentTypeID
WHERE f.AccountNumber = #AccoutNumber;"
Using connEType As New SqlConnection(ConfigurationManager.ConnectionStrings("WarrantyConnectionString").ToString)
Using cmdEType As New SqlCommand(TypeIDQuery, connEType)
cmdEType.Parameters.Add("#AccountNumber", SqlDbType.NVarChar).Value = txtSearchOC.Text
connEType.Open()
Dim rstData As New DataTable
rstData.Load(cmdEType.ExecuteReader)
If rstData.Rows.Count > 0 Then
' we have a valid match
lblPrefix.Text = "True"
Else
' we do not have a valid match
lblPrefix.Text = "False"
End If
End Using
End Using
End If
End Sub
So, pull the data into a data table. You can then check the row count, or even pull other values out of that one row. But, I don't see any need for some loop here.

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.

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

How to improve method for assigning grid permissions

I've got an ASP.NET (VB.NET) page that has an Infragistics grid on it. One of the columns is called 'status'.
When I load the grid, I'm setting permissions to the fields at the column level, granting user roles (ENUM 'UserTypes') read/only or read/write permissions.
Next I need to loop through each row and assign permissions based upon the value (ENUM StatusVals) in the field 'status' as well as the user role.
I've got all this working, but it seems clunky and I want to improve it.
Here's a snapshot of one of the methods in which I pass in a row, the record status for that row, and the user type and loop through the cells to assign the permissions and colors. The question is: is there a more elegant way to do this so that as I add to it, it doesn't become a beast?
Private Shared Sub SetDetailRowReadWrite_ByStatusVal(ByVal DetailRow As
ig.UltraGridRow, ByVal sv As StatusVals, ByVal UserType As UserRoles)
If sv = StatusVals.Pending _
OrElse sv = StatusVals.Released _
OrElse sv = StatusVals.Shipped _
OrElse sv = StatusVals.Consolidated _
OrElse sv = StatusVals.HOLD _
OrElse sv = StatusVals.Cancelled _
OrElse sv = StatusVals.PartialShipped Then
For Each column As ig.UltraGridCell In DetailRow.Cells
If column.Key = "StatusVal" Then
column.Style.BackColor = Drawing.Color.LightGreen
column.Style.ForeColor = Drawing.Color.Black
If UserType = UserRoles.Fulfillment Then
SetFulfillmentStatusValEditPermission(sv, column)
End If
ElseIf Not (sv = StatusVals.Consolidated AndAlso UserType = UserRoles.Fulfillment) Then
column.Style.BackColor = Drawing.Color.White
column.AllowEditing = ig.AllowEditing.No
End If
Next
LockSizesRow(DetailRow, UserType, sv)
ElseIf sv = StatusVals.Incomplete AndAlso UserType = UserRoles.Fulfillment Then
For Each c As ig.UltraGridCell In DetailRow.Cells
c.AllowEditing = UltraWebGrid.AllowEditing.No
Next
End If
End Sub
You may want to look into the LoginView control in ASP.Net (2.0 and above) it gives you a templated control that maps templates to ASP.Net roles, including not logged in (anonymous).
This QuickStarts, although a bit dated demonstrate a simple example but the point is made rather cleanly.

How can I remove nodes from a SiteMapNodeCollection?

I've got a Repeater that lists all the web.sitemap child pages on an ASP.NET page. Its DataSource is a SiteMapNodeCollection. But, I don't want my registration form page to show up there.
Dim Children As SiteMapNodeCollection = SiteMap.CurrentNode.ChildNodes
'remove registration page from collection
For Each n As SiteMapNode In SiteMap.CurrentNode.ChildNodes
If n.Url = "/Registration.aspx" Then
Children.Remove(n)
End If
Next
RepeaterSubordinatePages.DataSource = Children
The SiteMapNodeCollection.Remove() method throws a
NotSupportedException: "Collection is read-only".
How can I remove the node from the collection before DataBinding the Repeater?
Your shouldn't need CType
Dim children = _
From n In SiteMap.CurrentNode.ChildNodes.Cast(Of SiteMapNode)() _
Where n.Url <> "/Registration.aspx" _
Select n
Using Linq and .Net 3.5:
//this will now be an enumeration, rather than a read only collection
Dim children = SiteMap.CurrentNode.ChildNodes.Where( _
Function (x) x.Url <> "/Registration.aspx" )
RepeaterSubordinatePages.DataSource = children
Without Linq, but using .Net 2:
Function IsShown( n as SiteMapNode ) as Boolean
Return n.Url <> "/Registration.aspx"
End Function
...
//get a generic list
Dim children as List(Of SiteMapNode) = _
New List(Of SiteMapNode) ( SiteMap.CurrentNode.ChildNodes )
//use the generic list's FindAll method
RepeaterSubordinatePages.DataSource = children.FindAll( IsShown )
Avoid removing items from collections as that's always slow. Unless you're going to be looping through multiple times you're better off filtering.
I got it to work with code below:
Dim children = From n In SiteMap.CurrentNode.ChildNodes _
Where CType(n, SiteMapNode).Url <> "/Registration.aspx" _
Select n
RepeaterSubordinatePages.DataSource = children
Is there a better way where I don't have to use the CType()?
Also, this sets children to a System.Collections.Generic.IEnumerable(Of Object). Is there a good way to get back something more strongly typed like a System.Collections.Generic.IEnumerable(Of System.Web.SiteMapNode) or even better a System.Web.SiteMapNodeCollection?

Resources