Pages

-->

Wednesday, May 26, 2010

Add SourceSafe Add-In to VS.NET 2010

Ok for those out there that are still way behind on moving to Team Foundation Server and still use SourceSafe, you will find adding solutions to it in VS.NET 2010 is not enabled by default. To switch source control providers back to SourceSafe do the following: Goto 'Tools' -> 'Options' and select "Show all settings". Expand the "Source Control" node and then select "Plug-in Selection". On the right hand side change the value in the "Current source control plug-in:" to "Microsoft Visual SourceSafe" (pictured below).


Then sometime soon consider moving to Team Foundation Server, which has a "Basic" version that allows a route into a new provider for those intimidated by switching away from SourceSafe.

Monday, May 24, 2010

Using Caching on an Object Data Source and Making it Unique Per User

I am a fan of the Object Data Source control especially when binding objects to controls such as a GridView for ASP.NET web forms. Not a 'huge' fan, but a fan none the less. I find its ability to organize binding methods and events consistently makes it a decent option for binding, along with its ability to present the objects wired up in a strongly typed manner during configuration.

One of the nicest features of the ODS is its ability to implement caching. The ODS control will call it’s wired up '.Select()' method on the object upon binding. For (2) main reasons I can see how caching can be useful.

1. Use ODS caching to prevent multiple users pulling the 'exact' same static data. Caching is an application level store, so once ODS data is stored in the Page's cache, it can be shared to all client's. Why make 20 calls to the database amongst 20 clients for the identical data? This is a good candidate for caching.

2. Use ODS caching when the user is pulling back several records (i.e. 500-1000) records to be displayed in a Gridview, but don’t want the .Select()method called every time the Grid is paged. Essentially getting all 1000 records on each paging command, but yet only displaying maybe 10 at a time on the screen. With caching, all 1000 are pulled back the 1st time, but subsequent paging calls in the bound GridView only have to access the cache for the data and not call the database again. Another good candidate for caching.

If you only need caching for the purpose of the situation described in #1 above then you can stop and are done. This is the way it appears Microsoft designed the caching in the ODS, and really the only thing you need to do is set 'EnableCaching="True"' on the ODS control. It was well designed for the scope of sharing the same data via the cache for multiple clients running the ASP.NET web application.

But what about #2 above. The tricky part is the Cache object is an Application level store shared amongst all clients. What if you still wanted to implement caching for an individual user that requested the 1000 records via some search criteria, but yet don't want that same 1000 record pulled for a different user attempting to pull data based on another criteria. This is exactly what will happen with the default behavior. User2 would get User1's records because the cache is shared by the same ODS between applications. This is easy to test by opening 2 browsers on the same or separate machines. Have User1 do a custom search that populates a GridView, and then have User2 do the same but with different criteria. Upon User1 paging the GridView after User2's results are displayed, User1 will see User2's results. It sure would be nice if there was a "MakeCachingVaryByUser" True/False option on the ODS but it does not exist.

So we need a work around so that we can still leverage caching but make it unique per user. Initially I thought this might be as easy as assigning a unique value to the "CacheKeyDependency" value at runtime server side and hoping data stored in the cache would be based on that unique key for the user, but that did not work. Upon doing testing of this scenario I came to find out the ODS control has some undocumented idiosyncrasies that make it cumbersome to work with at times. The 1st of these was determining that no matter what value I tried to set for the 'CacheKeyDependency' property (or any ODS cache related property) at runtime say in Page_Load() would not stick. Only the value assigned in the page's source was used by the ODS and continually referred to. I tested this by placing a label on a page and writing to it the value of the "CacheKeyDependency" and "CacheDuration". On Page_Load these values would display whatever was assigned in the Page's source. Upon overwriting them, you could verify for a moment the new values were indeed assigned, but upon any subsequent postback to the server, the ODS reverted back to its default values assigned in the source. Well then I thought, I will just store the values in ViewState or in a HiddenField and continually overwrite the ODS properties on all postbacks, right?? Nice try, but still didn’t work. It kept pointing me to the fact that whatever was assigned in the source is what the ODS was married to, and wasn’t going to allow any changes.

So my next step was to determine, "How can I somehow assign a dynamic value to those properties in the page's source, so that a unique cache allocation will be created per user?" This is where the solution comes into play. I must also stop and mention what combination of properties make a 'unique' combination for the ODS according to the MSDN documentation. Let's take a look:

"A unique cache entry is created for every combination of the CacheDuration, CacheExpirationPolicy, TypeName, SelectMethod, and SelectParameters properties."

Ok, between my users CacheExpirationPolicy, TypeName, SelectMethod, and SelectParameters (the actual parameters not the values of the parameters) are always going to be the same. The one that can change... 'CacheDuration'. This specifies a time in seconds before the cache is expired and the ODS will call the select method again. Since caching to me is a 'helpful' attribute of the app, and not a requirement, I do not care too much about this value as long as something reasonable is assigned. And wait! Is the "CacheKeyDependency" value not used to create a unique combination?? That's correct it is not. But we are still going to dynamically create the value as we are the "CacheDuration" to ensure a unique client combination. And assigning the "CacheKeyDependency" to a unique value actually bypassed some odd behavior of calling the .Select() method each time a separate client made a new search, so the combination of both being dynamically created makes up the solution.

Now to the code. Let's begin with the source behind the controls. We need to use inline server tags that actually are used for DataBound controls. We can specify some TimeSpan attributes like 'TimeOfDay' or 'Ticks' to generate some unique values. We use the String.Format function with a placeholder, passing in the TimeSpan method chosen. Foe the "CacheKeyDependency" that actual type is string, so it can be long and we don’t have to worry too much about size. We will use Now.Ticks() for this. The cache duration is an Integer value in seconds so we don’t want it to surpass the bounds; for this we will use Now.TimeOfDay.Milliseconds().

EnableCaching="true"
CacheExpirationPolicy="Sliding"
CacheKeyDependency='<%# String.Format("{0}", Now.Ticks()) %>'
CacheDuration='<%# String.Format("{0}", Now.TimeOfDay.Milliseconds()) %>'
Now in order for the above to resolve to their actual values, we must call the .DataBind() method on the ODS control. This actually ends up serving a dual purpose as it will ensure the .Select() method is called each time the page initially loads, and it will resolve our server code into usable values. If we didn't call .DataBind, we would end up showing the string literal from the property assignment which is no good. Here is the code that needs to be placed in the Page_Load method:

If Not IsPostBack Then

'Call .DataBind() on the Page so the Properties on the ODS below that have serverside
'code associated to the 'CacheKeyDependency' & 'CacheDuration' properties will resolve.
Me.odsItems.DataBind()

'Expire the cache entry programmatically by expiring the key. The key can be expired by using the
'Cache.Remove method with the current CacheKeyDependency value as the parameter.
'When the cache item is removed, all the cached data that is dependent on the key is expired.
'This will force a fresh pull by calling the ODS's wired up Select() method again.
Cache.Remove(Me.odsItems.CacheKeyDependency)
'It's imperative that the ODS 'CacheKeyDependency' value exists in the Page's cache,
'otherwise the data cached by the ODS control will be immediately evicted from the data cache
'each time its added. It is added implicitly in the line below if it does not already exist.

'Using the key value reference of the ODS in the Page's cache, assign an arbritarty new value.
'This process expires the current cache assosiated with the ODS which will force new data to be pulled.
'If this is not done, subsiquient databound control events or page interactions will pull data
'from cache to populate the bound control as opposed to requesting new data.
Cache(Me.odsItems.CacheKeyDependency) = DateTime.Now

'Store both values in hidden field controls so subsiquient server calls can reload the same values.
'This will need to be done, because the inline server code property attributes only resolve when Page.DataBind() is called.
'This way we do it once above, and then just reload the values later.
Me.hfODSCacheKey.Value = Me.odsItems.CacheKeyDependency
Me.hfODSCacheDuration.Value = Me.odsItems.CacheDuration.ToString()

End If


'The 'CacheKeyDependency' & 'CacheDuration' values MUST be reset on all server calls so the default
'databound value will be used as assigned on the ODS directly. Since the Page is only
'databound in initial PageLoad, these values can just be reassigned from their corresponding HiddenField values.
'This MUST be done or the cache would revert to the default literal value from the control which will not
'match because it uses inline server-side code to generate dynamic values so the cache essentially is user specefic.
Me.odsItems.CacheDuration = Me.hfODSCacheDuration.Value
Me.odsItems.CacheKeyDependency = Me.hfODSCacheKey.Value
After the databind, we evict the cache using the newly assigned values to make sure that one it exists in the Page's cache, and two that there is nothing assigned (or actually an arbitrary DateTime value). Next we assign (2) hidden field controls (or ViewState - just needs to be page specific; don't use Session for these values) the values of the generated "CacheKeyDependency" and the "CacheDuration". This will be pulled on subsequent post backs and reassigned to the ODS. But wait a minute??? I know what you are thinking - you said reassigning the ODS properties with custom values didn't work, right? Yes, but these values match the values initially assigned in the source to the ODS, as opposed to overwriting the values with newly created ones. This is that undocumented feature that we are attempting to adhere to.

Lastly, notice the code that will reassign the "CacheKeyDependency" & "CacheDuration" properties on the ODS on each postback. This ensures that the ODS is placing data in the proper cache location, and it must be done or the literal value from the server tags would be used and we will lose reference to our data. The important piece here is that the initial value assigned in source to the ODS is continually reassigned.

At this point that's all the code you need! I recommend if doing this on several pages that you refactor the code above into a 'Shared' Utility UI method. You may end up needing to pass in the Page, Cache, ODS, and ViewState objects in order to refactor to a centralized method outside the page. You can do this later after everything is working well and tested.

The last piece of code will allow you to forcibly evict the cache. Why would you want to do this? If you had a GridView based on search criteria, and a new search was made. In this case you don’t want the default behavior of grabbing data from the cache; you want new data pulled. There are several other reasons you may want to force a fresh data pull, so inject the code below where needed.

'Expire the current cache assosiated with the ODS which will force new data to be pulled.
'If this is not done, this process would not yield new results as the ODS would
'continue to pull existing data from the cache; in our case we want new data.
PageCache.Remove(Me.odsItems.CacheKeyDependency)
PageCache(Me.odsItems.CacheKeyDependency) = DateTime.Now
The (2) lines above are another good candidate for code refactoring into a centralized Shared UI method (i.e. Utilities.ClearODSCache, etc.). Once again you might need to pass in the ODS control and PageCache objects in order to refactor outside the page.

So in just a few lines of code we have modified the caching offered by the IDS control to be user specific. This way we get the performance and caching attributes provided by caching, but the low level scope we needed. There are other 'big picture' ways to probably solve this same issue. For example, don't use the IDS at all, and just persist the Object source for DataBinding in Session and constantly access it there as opposed to going to the Database. Another option might be to siphon 'e.Result' in the 'Completed' event of an ODS and store that in Session. Then on subsequent calls to the '.Selecting()' method, check to see if an object exists as the DataSource already and cancel the operation. There are probably others too, but the ones mentioned here have pitfalls within, by using larger scale techniques to solve a specific issue. I prefer the method of this post because it is specific to addressing how to modify the caching ability to be unique per user. If you would like to learn more about how native caching works for the ODS control, please view the link below.

Caching Data with the ObjectDataSource

Tuesday, May 18, 2010

Methods for Comparing Lists of Objects Based on a Single Property

Recently I had the need to get a new list of objects that results in a new list with all of the items in 'MyList1' that don't already exist in 'MyList2'. At 1st glace I thought I could use the Enumerable.Except function to accomplish this. However I soon came to realize that if the class representing this list has 10 properties, all 10 properties are checked to find the difference. This does make sense and works as intended, but in my case the results were not what I wanted because one of the properties was a timestamp, and even though an object in MyList1 had the same 'ID' as an object from 'MyList2', its timestamp property was different thus retuning that object into the new list as well. What I needed to do was base the returned list results on a single property: 'ID'. This is all that I cared about, so the default overload of the .Except method of an IEnumberable type was not going to work.

A few methods presented themselves on how to solve this issue. If you are reading this through and you do not need to discriminate differences of objects for a single or few properties and it is a strict 1:1 comparison, you are done! Just use the .Except method as shown below:

Dim MyList1 As New List(Of Customer)
Dim MyList2 As New List(Of Customer)
'...populate the above lists with data
Dim ItemsInList1NotInList2 As New List(Of Customer)
'Get all of the items in MyList1 that do not already exist in MyList2 using the .Except() method
ItemsInList1NotInList2 = MyList1.Except(MyList2)
The 1st method in solving this issue is to use the second overload of the .Except method to define the IEqualityComparer(Of T) to compare values. This involves implementing the IEqualityComparer on the class being compared and defining the methods required by the interface for a custom comparer. Now in my case the comparison was for a specific case and I wanted a solution that was more inline. I wouldn't want to define a compare method for the class unless it was definite that this was always how the class was to be compared. In my situation this was not true so I did not implement the interface, but check out the link below for an explanation and code example on implementing the IEqualityComparer interface:

Enumerable.Except(Of TSource) Method

The 1st of (2) methods that worked for a more localized inline solution where to use the methods exposed on an IQueryable source, such as the .Where() and .Select() methods in the System.Linq namespace. By calling the .Where method as an instance method on MyList1 we can define a predicate that will test for a condition and return the resulting values. In our case we want to pass into the Where() method a Lambda expression that will use anonymous functions to test for a condition and return the resulting values. Let's look at the code:

Dim MyList1 As New List(Of Customer)
Dim MyList2 As New List(Of Customer)
'...populate the above lists with data
Dim ItemsInList1NotInList2 As New List(Of Customer)
'Get all of the items in MyList1 that do not already exist in MyList2 using the .Where() method
ItemsInList1NotInList2 = MyList1.Where(Function(i) (MyList2.Select(Function(i2) i2.ID).Contains(i.ID) = False)).ToList()
So the above code can loosely be read from the inside out as "Select all IDs from MyList2 that do not exist in MyList1 and return them into a list of type MyList1". The .Where() method returns the elements from source that satisfy the condition specified by predicate. A predicate is a function that will test each element for a condition returning a Boolean value. In our case the Boolean value returned is True/False if the .ID in MyList2 exists in MyList1. Also notice the .Select() method called on MyList2 to project over the sequence of .ID values and use the index of each element in the projected form. Both IQuerable methods define an Anonymous method in VB.NET using the 'Function()' keyword accepting parameter of the type to be used. In our case the .Where() method takes the anonymous type 'i' which is of type MyList1 and the .Select() method takes an anonymous type 'i2' of type MyList2. If we were to decompile the above code we should see all of the values in MyList2 being iterated over to determine if the .ID value already exists in MyList1 and if not adding it to a new list of type MyList1 that is the result. Just remember that Lambda expression are just syntax sugar. In our case you could write the same code long hand by defining a delegate method that takes in the list types and iterates through them to get the same result. The Lambda expressions make our life as developers much easier by not having to write out so much code.

To me I probably prefer the method above to solve this issue because it is the most concise. However, those not familiar with anonymous types and Lambda expressions may not read the code above so well and want something a bit more explicit in definition. The 2nd method to solve our issue is to define a simple LINQ query dumping the result into a an anonymous type and then converting it into a list. I think the advantage of this 2nd method is it is much more readable. Let's take a look at the code:

Dim MyList1 As New List(Of Customer)
Dim MyList2 As New List(Of Customer)
'...populate the above lists with data
Dim ItemsInList1NotInList2 As New List(Of Customer)
'Get all of the items in MyList1 that do not already exist in MyList2 using a LINQ query
Dim query = From ItemsIn1 In MyList1 _
Where Not (From ItemsIn2 In MyList2 _
Select ItemsIn2.ID).Contains(ItemsIn1.ID) _
Select ItemsIn1
'Convert the LINQ query results into a list of objects
ItemsInList1NotInList2 = query.ToList()
Even though LINQ should not be confused with TSQL, the above query does have attributes of a typical SQL query. It is essentially a 'NOT' clause with LINQ sprinkled in. It reads loosely as follows: "Select all of the items in MyList2 that are also in Mylist1, and exclude these items (using NOT) from everything that is in MyList1; finally Select the results. I think another advantage of this is it is faster to modify if you needed to make the comparison based on (2) properties as opposed to (1) as in my examples. Both solutions work identically and will produce the same results.

So to review, we discussed (3) methods for comparing (2) lists to get only the items in the 1st list that don't exist in the 2nd list: the Enumerable.Except() method, the Queryable.Where() method, and a LINQ query. Each has their place, but all will help to quickly make a comparison that otherwise may have required a long had For-Each loop with a flag set for comparison differences in order to get the same result.

Wednesday, May 12, 2010

Using a Child Object's Property in a GridView Bound to an Object Data Source

The Object Data Source control is a nice way in ASP.NET webforms to bind a GridView control to a business object. It helps organize the method calls and events associated with the object and does save a lot of coding that must otherwise be done manually. Another benefit of binding a GridView to an ODS is that it will automatically create all of the bound columns in your GridView based on the properties on the object. So in the class below, FirstName, LastName, and Address will be displayed in the GridView.

Public Class Customer

Private mFirstName As String = String.Empty
Private mLastName As String = String.Empty
Private mAddress As String = String.Empty
Private mCustomerOrder As Order = Nothing

Public Sub New()
'Default class Constructor
End Sub

Public Property FirstName() As String
Get
Return mFirstName
End Get
Set(ByVal value As String)
mFirstName = value
End Set
End Property

Public Property LastName() As String
Get
Return mLastName
End Get
Set(ByVal value As String)
mLastName = value
End Set
End Property

Public Property Address() As String
Get
Return mAddress
End Get
Set(ByVal value As String)
mAddress = value
End Set
End Property

Public Property CustomerOrder() As Order
Get
Return mCustomerOrder
End Get
Set(ByVal value As Order)
mCustomerOrder = value
End Set
End Property

End Class
However notice the class property above named 'CustomerOrder' which is of type 'Order' and is an object instance property on the Customer Object. The 'Order' class contains a property named 'ItemName' that you would like to also have bound to your GridView. By default the ODS control will not map these properties on the child object for you. So how do we access this child object's properties and use it as well? Well with a little help from the forums, we find that you can add a 'TemplateField' column to your GridView, and access the field through data-binding expressions with a [ChildObject.PropertyName] syntax. Let's take a look below how this can be accomplished:







Using the Data-Binding 'Eval()' expression we are able to have a one-way read only binding of the 'ItemName' property. If you needed an updateable column you could use the 'Bind()' expression. The Eval method evaluates late-bound data expressions in the templates of the GridView control. If you need more information about the Eval() and Bind() data-binding expressions in VB.NET, take a look to the following MSDN link:

Data-Binding Expressions Overview

One last point to mention is concerning the ODS control. This post syntactically and programmatically is the same for those of you that manually bind a GridView control and do not use an ODS. The main elements here were the type of column used 'TemplateField' and the Data-Binding expressions 'Eval()' to access the child object's properties.