Object references and cache - asp.net

I have a function GetAllProducts() which fetches all products from a database and stores it in the cache for future requests. This works fine, but if I then call the function e.g. ProductSearchResults = GetAllProducts(), and then modify ProductSearchResults variable, this also modifies the cache, which is very important this never happens, as the cache affects the whole website.
I understand this is because both ProductSearchResults and the cache now have the same reference, but how do I solve the problem? Is there something that I can put in GetAllProducts() to ensure the cache always uses its own value?
Public Shared Function GetAllProducts() As ProductCollection
Dim Products As New ProductCollection()
If IsNothing(System.Web.HttpContext.Current.Cache("ProductData")) Then
'////// Database code to get products goes here //////
System.Web.HttpContext.Current.Cache.Insert("ProductData", Products, Nothing, DateTime.Now.AddMinutes(5), TimeSpan.Zero)
End If
Products = System.Web.HttpContext.Current.Cache("ProductData")
Return Products
End Function
Public Shared Function SearchProducts(ByVal SearchText As String) As ProductCollection
Dim ProductSearchResults As ProductCollection = Nothing
If SearchText <> "" Then
SearchText = SearchText.ToLower()
Dim Keywords As New ArrayList()
Keywords.AddRange(SearchText.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
ProductSearchResults = GetAllProducts()
For i As Integer = 0 To Keywords.Count - 1
For j As Integer = ProductSearchResults.Count - 1 To 0 Step -1
If ProductSearchResults(j).ProductName.ToLower.Contains(Keywords(i)) = False Then
ProductSearchResults.RemoveAt(j)
End If
Next
Next
End If
Return ProductSearchResults
End Function

This is because you are essentially returning a collection of pointers to the object thats in the cache. You could implement IClonable on your object and have a Function that returns a new collection with cloned objects.
Public Function GetClonedObjects() As ProductCollection
Dim myCollection As New List(Of MyObject)
For Each item as Product in GetProducts()
myCollection.Add(item.Clone)
Loop
Return myCollection
End Function
Or Create a property to hold a cloned copy of the collection
Private _clonedProducts As ProductCollection = Nothing
Public ReadOnly Property ClonedProducts As ProductCollection
Get
If _clonedProducts Is Nothing Then
_clonedProducts = New ProductCollection
For Each item As Product In GetAllProducts()
_clonedProducts.Add(item.Clone())
Next
End If
Return _clonedProducts
End Get
End Property

Related

How to sort properties of object and populate into an ArrayList

How can I sort the following properties in the class then store in an ArrayList?
I have populated that to an ArrayList but there an issue when page is reloaded items order in the repeater will change. here is population cookies value in to an array
Dim myCookies As HttpCookie=HttpContext.Current.Request.Cookies("Mycard")
Dim varArryItems As ArrayList = New ArrayList
For i AsInteger=0 To varCookies.Values.Count-1
Dim AllValues As String()=myCookies.Values(i).Split("|"c)
Dim item As objCard=New objCard
item.P_ItemID=Integer.Parse(AllValues(0))
item.P_ItemTitle=AllValues(1).ToString
item.P_BrandTitle=AllValues(2).ToString
item.P_ItemImg=AllValues(3).ToString
item.P_ItemPrice=Decimal.Parse(AllValues(4))
'item.P_ItemQauntity=Integer.Parse(AllValues(5))
'item.P_ItemQauntitySelected=Integer.Parse(AllValues(6))
item.P_BarcodeID=Integer.Parse(AllValues(7))
item.P_TotalItemPrice=Decimal.Parse(AllValues(8))
varArryItems.Add(item)
Next
rptcart.DataSource=varArryItems
rptcart.DataBind()
Here is my objCard class store cookies values I need sort all properties I have tried suing ArrayList Sort method it wasn't work for me.
Public Class objCard
Private ID As Integer
Private ItemID As Integer
Private BarcodeID As Integer
Private ItemTitle As String
Private BrandTitle As String
Private ItemImg As String
Private ItemPrice As Decimal
Private TotalItemPrice As String
Private ItemQauntity As Integer
Private ItemQauntitySelected As Integer
Public Property P_ID As Integer
Get
Return Me.ID
End Get
Set
Me.ID = Value
End Set
End Property
Public Property P_ItemID As Integer
Get
Return Me.ItemID
End Get
Set
Me.ItemID = Value
End Set
End Property
Public Property P_BarcodeID As Integer
Get
Return Me.BarcodeID
End Get
Set
Me.BarcodeID = Value
End Set
End Property
Public Property P_ItemTitle As String
Get
Return Me.ItemTitle
End Get
Set
Me.ItemTitle = Value
End Set
End Property
Public Property P_BrandTitle As String
Get
Return Me.BrandTitle
End Get
Set
Me.BrandTitle = Value
End Set
End Property
Public Property P_ItemImg As String
Get
Return Me.ItemImg
End Get
Set
Me.ItemImg = Value
End Set
End Property
Public Property P_ItemPrice As Decimal
Get
Return Me.ItemPrice
End Get
Set
Me.ItemPrice = Value
End Set
End Property
Public Property P_TotalItemPrice As String
Get
Return Me.TotalItemPrice
End Get
Set
Me.TotalItemPrice = Value
End Set
End Property
Public Property P_ItemQauntity As Integer
Get
Return Me.ItemQauntity
End Get
Set
Me.ItemQauntity = Value
End Set
End Property
Public Property P_ItemQauntitySelected As Integer
Get
Return Me.ItemQauntitySelected
End Get
Set
Me.ItemQauntitySelected = Value
End Set
End Property
End Class
If you have
Public Class Card
Property ID As Integer
Property ItemID As Integer
Property BarcodeID As Integer
Property ItemTitle As String
Property BrandTitle As String
Property ItemImg As String
Property ItemPrice As Decimal
Property TotalItemPrice As Decimal
Property ItemQuantity As Integer
Property ItemQuantitySelected As Integer
End Class
Then you can use a List(Of Card) to store the data. This has the advantange that the compiler knows that it has instanced of Card in it instead of just some object.
Dim myCookies As HttpCookie = HttpContext.Current.Request.Cookies("Mycard")
Dim cards = New List(Of Card)
For i As Integer = 0 To varCookies.Values.Count-1
Dim allValues As String() = myCookies.Values(i).Split("|"c)
Dim item = New Card
item.ItemID = Integer.Parse(allValues(0))
item.ItemTitle = allValues(1).ToString
item.BrandTitle = allValues(2).ToString
item.ItemImg = allValues(3).ToString
item.ItemPrice = Decimal.Parse(allValues(4))
'item.ItemQuantity = Integer.Parse(allValues(5))
'item.ItemQuantitySelected = Integer.Parse(allValues(6))
item.BarcodeID = Integer.Parse(allValues(7))
item.TotalItemPrice = Decimal.Parse(allValues(8))
cards.Add(item)
Next
And now that the compiler can get to the properties of the entries in the list, you can
Dim dataToPresent = cards.OrderBy(function(c) c.ItemId).ToList()
rptcart.DataSource = dataToPresent
rptcart.DataBind()
and it will show the data in the order you chose.
If you need to order by different properties at run-time then a search for "linq dynamic orderby" should give you useful code.
I noticed that you had Private TotalItemPrice As String which conflicted with item.P_TotalItemPrice=Decimal.Parse(AllValues(8)). If you use Option Strict On then Visual Studio will point out problems like that for you.
P.S. You have Dim myCookies but you use varCookies.Values.Count. You may want to check that that is correct.

How to Compare nullable objects

I need to compare a current List of Objects from a DB against a new List of Objects. I want to compare them and highlight for the user those which have changed (in this case, return TRUE that they are different).
Since some of my objects are Nullable, this involves a lot of IF NOT IS Nothing on the side of the NewObj and the CurrentObj...I've trying to find a more efficient way of writing the below as I have to use it to compare about 30 objects of different types, IE Date, Decimal, Int, etc..
The below works until say either of the Obj has no Rank and is therefore Nothing
Suggestions?
Dim Rank As Boolean = CompareData(NewObj, CurrentObj, "Rank")
Dim Regiment As Boolean = CompareData(NewObj, CurrentObj, "Rank")
Dim DateofBirth As Boolean = CompareData(NewObj, CurrentObj, "DoB")
Private Function CompareData(NewObj As Business.Casualty, CurrentObj As Business.Casualty, FieldToComapre As String) As Boolean
Select Case FieldToComapre
Case "DateOfBirth"
Return (Nullable.Equals(NewCasualty.DateOfBirth, CurrentCasualty.DateOfBirth))
Case "Age"
Return (Nullable.Equals(NewCasualty.Age, CurrentCasualty.Age))
Case "Rank"
Return (Nullable.Equals(NewCasualty.Rank.ID, CurrentCasualty.Rank.ID))
Case "Regiment"
Return (Nullable.Equals(NewCasualty.Regiment.ID, CurrentCasualty.Regiment.ID))
Case Else
Return True
End Select
End Function
My first suggestion is to change the name of your function to something like AreFieldValuesTheSame. My second suggestion is to negate the purpose of the function. In other words, return True when the values are the same.
If you follow my first two suggestions, then for each one of your cases, instead of simply
Return (Nullable.Equals(a.ID, b.ID))
you need something more like
Return ((a Is Nothing And b Is Nothing) Or (a.ID Is Nothing And b.ID Is Nothing) Or (Nullable.Equals(a.ID, b.ID)))
For example, a represents NewCasualty.Rank and b represents CurrentCasualty.Rank. You need to check for the object being Nothing (null in other words) before you try to check properties of the object.
Also, at the beginning of the function, you need to check whether NewObj Is Nothing and whether CurrentObj Is Nothing:
If (NewObj Is Nothing) And (CurrentObj Is Nothing) Then
Return True
Else If (NewObj Is Nothing) Or (CurrentObj Is Nothing)
Return False
Please forgive me if my VB syntax is not perfect. (I'm much more fluent in C#.)
I actually ended up just going with an Extension method..it was simpler and cleaner IMO.
Dim Rank As Boolean = CompareData(NewObj, CurrentObj, "Rank")
Dim Regiment As Boolean = CompareData(NewObj, CurrentObj, "Regiment")
Dim Trade As Boolean = CompareData(NewObj, CurrentObj, "Trade")
Private Function CompareData(NewObj As Business.Casualty, CurrentObj As Business.Casualty, FieldToComapre As String) As Boolean
Select Case FieldToComapre
Case "Trade"
Return NewCasualty.Trade.NullableEquals(CurrentCasualty.Trade)
Case "Rank"
Return NewCasualty.Rank.NullableEquals(CurrentCasualty.Rank)
Case "Regiment"
Return NewCasualty.Regiment.NullableEquals(CurrentCasualty.Regiment)
Case Else
Return True
End Select
End Function
public static class NullableCompare
{
public static bool NullableEquals<T>(this T s1, T s2)
where T : class
{
if (s1 == null)
{
return s2 == null;
}
return s1.Equals(s2);
}
}
public partial class Rank
{
public override bool Equals(object obj)
{
var p2 = obj as Rank;
if (p2 == null)
{
return false;
}
if (this.ID != p2.ID)
{
return false;
}
return this.ID == p2.ID;
}
public override int GetHashCode()
{
return this.ID.GetHashCode();
}
}

How can I assign a property value based on another property in a composite control?

Environment: Asp.net 4.5, Webforms
I'm creating a composite control. I've exposed multiple public properties, but running into a slight problem.
Let's say I have two properties:
Public Property Path() As String
Get
Return ViewState("Path")
End Get
Set(ByVal Value As String)
If UseAbsolute = True Then
' do something
Else
' it always lands heere...
End If
End If
ViewState("Path") = Value
End Set
End Property
Private _Path As String = String.Empty
Public Property UseAbsolute() As Boolean
....
End Property
Private _UseAbsolute As Boolean = False
My controls are being assigned values on PreRender. The problem is, when I call "Path" it's getting the default/private value for UseAbsolute. So even if I set the property to True in the control/html, it grabs the false first.
I can work around this many ways, but I feel I'm missing a proper method or understanding.
UPDATE
I forgot to mention. I am:
EnsureChildControls()
in the PreRender...
I also tried adding this to the properties themselves.
Shouldn't Your code be like this instead of how you have it?
Private _Path As String = String.Empty
Private _UseAbsolute As Boolean = False
'
Public Property Path() As String
Get
Return _Path
End Get
Set(ByVal Value As String)
If UseAbsolute = True Then
' do something
_Path = some_absolute_path
Else
' do something else
_Path = some_relative_path
End If
End Set
End Property
'
Public Property UseAbsolute () As Boolean
Get
Return _UseAbsolute
End Get
Set(ByVal Value As Boolean)
_UseAbsolute = Value
End Set
End Property

Efficient way to check if Columns exist in the DataReader

There are tables with over 50 columns, I am using following code to loop through the dataReader for column existence.
If HasColumn(reader, "EmpCode") Then obj.OwningOfficeID = CType(reader("EmpCode"), Int32)
Protected Function HasColumn(ByRef reader As SqlDataReader, ByVal columnName As String) As Boolean
For i As Integer = 0 To reader.FieldCount - 1
If reader.GetName(i).Equals(columnName) Then
Return Not IsDBNull(reader(columnName))
End If
Next
Return False
End Function
I am wondering if there is any better way of checking the columns in the DataReader instead of looping through the DataReader each time i bind the object property?
SqlDataReader.GetSchemaTable Method will give the DataTable of the executed query, from there you can get all the columns
Returns a DataTable that describes the column metadata.
MSDN http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.getschematable.aspx
currently I am using this extension method for this purpose
public static bool TryGetOrdinal(this IDataRecord dr, string column, out int ordinal)
{
try
{
ordinal = dr.GetOrdinal(column);
}
catch(Exception ex)
{
ordinal = -1; //Just setting a value that GetOrdinal doesn't return
return false;
}
return true;
}
so you use it as follows
int ordinal = 0;
if(dr.TryGetOrdinal("column",out ordinal))
string val = dr.GetValue(ordinal).ToString();
My VB is not so good. Sorry couldn't convert it to VB for you. But I think you get the basic idea and can recode it in VB.

Declare return Type of the Function when there are two possible returned types

I have the following statement, I want to turn it into a Public Shared Function :
If isEmployee Then
Dim employeeInstance As New Employee
employeeInstance = GetEmployeeInstanceByUserId(userId)
Return employeeInstance
Else
Dim studentInstance As New Student
studentInstance = GetStudentInstanceByUserId(userId)
Return studentInstance
End If
Public Shared Function GetWorkerInstance(Byval isEmployee as Boolean) As ...(not sure what to write here)...
There two possible return Type. I'm not sure what I should declare for the function return type.
Any suggestion? Thank you.
This would be easiest if both the Employee and Student classes derived from one parent (either a base class or interface), then you could use that as your return type.
You can't declare different return types on the one function and you will not be able to create overrides that return different types, as the method signature resolution does not consider return types.
Generic
Private Class foo
Dim s As String = "FOO"
End Class
Private Class bar
Dim s As String = "BAR"
End Class
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim o As Object = GetWorkerInstance(True)
If TypeOf o Is foo Then
Stop
Else
Stop
End If
End Sub
Public Shared Function GetWorkerInstance(ByVal isEmployee As Boolean) As Object
If isEmployee Then Return New foo Else Return New bar
End Function

Resources