February 9, 2010, 9:23 pm
The great thing about fetching data via a LINQ to SQL query is that you get a nice formatted result and you can easily save back any changes you make with SubmitChanges(). Unfortunately, we all inevitably fall into scenarios where we have to make use of stored procedures for performance or other reasons. If you have a stored procedure whose result set contains columns from just a single table then you can easily map the stored procedure to that table, but in most cases the result set involves multiple tables making things a little more tricky. It’s easy to execute a stored procedure from LINQ to SQL (just drag the SP from the server explorer into the designer and then execute it like a function on the data context) but you lose some of the benefits of LINQ to SQL. First of all, you just get a flat result set instead of a hierarchical result set using the auto generated entity classes. Second, you can’t just make changes to the result and call SubmitChanges. Luckily, with a little extra work, the flat, detached result set can be converted into a hierarchical, attached result set where changes can easily be saved.
If you don’t want to bother reading the whole article and all of the code, here’s the short answer: use the Attach() method.
Below is an example that runs a stored procedure to return all users in the database joined with their articles. The results are converted into an attached list of users, each containing a collection of articles. Notice that not all of the columns need to be known, just the primary key and timestamp are required. For more info on the timestamp, check out this article.
Module Module1
Sub Main()
Using testContext As New TestDataContext
' Print SQL queries to the console for testing purposes
testContext.Log = Console.Out
' Get attached entities
Dim users = GetAttachedUsersWithGroups(testContext)
' Make some random changes to prove the concept
users.First.userName = "foo"
users.First.Articles.First.text = "bar"
' Submit the changes to see what SQL gets executed
testContext.SubmitChanges()
End Using
Console.ReadKey()
End Sub
Public Function GetAttachedUsersWithGroups(ByVal context As TestDataContext) As IEnumerable(Of User)
' Get some data from a stored procedure
Dim result = context.GetAllUsersWithArticles
' Convert flat result set to groups of articles by user
Dim userGroups = From row In result _
Group row By row.userID, row.userTimestamp _
Into articles = Group _
Select userID, userTimestamp, articles
Dim users As New List(Of User)
' Create LINQ to SQL entities
For Each userGroup In userGroups
Dim user As New User With {.userID = userGroup.userID, _
.ts = userGroup.userTimestamp}
For Each article In userGroup.articles
user.Articles.Add(New Article With {.articleID = article.articleID, _
.title = article.title, _
.ts = article.articleTimestamp})
Next
users.Add(user)
Next
' Attach the users to the data context. This will also attach the articles
' because they have been added to each user's Articles collection.
context.Users.AttachAll(users)
Return users
End Function
End Module
February 9, 2010, 9:00 pm
LINQ to SQL has built in optimistic concurrency checking. When you create an unattached entity and then attach it (ie: with the Attach() function) the concurrency check will always fail by throwing a ChangeConflictException unless one of the two are true:
- The table that the entity belongs to has a timestamp column and its value is exactly the same as it appears in the database.
- There is no timestamp column but the “no count” feature on SQL Server is off.
Using a timestamp column seems like the more elegant solution, but it does require that you know the timestamp value. This usually means that if you’re attaching the result of a stored procedure so that you can save back the results, your stored procedure needs to return the timestamp in addition to the primary key.
February 5, 2010, 7:26 pm
In LINQ to SQL you can chain multiple where clauses like this:
Module Module1
Sub Main()
Using context As New TestDataContext
context.Log = Console.Out
Dim articles = context.Articles.Where(Function(a) a.articleID > 10) _
.Where(Function(a) a.articleID Mod 2 = 0) _
.ToList()
End Using
Console.ReadKey()
End Sub
End Module
This will generate SQL that looks roughly like this:
SELECT ... FROM Articles WHERE articleID > 10 AND articleID % 2 = 0
Since chained where clauses are equivalent to ANDing multiple expressions in a single WHERE, the above SQL is exactly what you would expect to see. Unfortunately, things get more complicated when one of the expressions cannot be converted to SQL, like in this case:
Module Module1
Sub Main()
Using context As New TestDataContext
context.Log = Console.Out
Dim articles = context.Articles.Where(AddressOf FilterArticle).ToList()
End Using
Console.ReadKey()
End Sub
Function FilterArticle(ByVal a As Article) As Boolean
Return a.articleID Mod 2 = 0
End Function
End Module
The above code generates SQL that looks like this:
The query has no where clause, it just loads all the articles and then filters them on the client side. It’s usually optimal to do the filtering on the SQL side, but the behaviour is reasonable. I wouldn’t expect the ORM to be capable of magically converting the contents of the FilterArticle function into SQL (it sure would be nice though). This is still expected behaviour, but here’s an example where things get weird:
Module Module1
Sub Main()
Using context As New TestDataContext
context.Log = Console.Out
Dim articles = context.Articles.Where(AddressOf FilterArticle) _
.Where(Function(a) a.articleID > 10) _
.ToList()
End Using
Console.ReadKey()
End Sub
Function FilterArticle(ByVal a As Article) As Boolean
Return a.articleID Mod 2 = 0
End Function
End Module
This code generates the same SQL as last time:
It is filtering both where clauses on the client side even though the second one could have been converted to SQL. If you flip the where clauses like this:
Module Module1
Sub Main()
Using context As New TestDataContext
context.Log = Console.Out
Dim articles = context.Articles.Where(Function(a) a.articleID > 10) _
.Where(AddressOf FilterArticle) _
.ToList()
End Using
Console.ReadKey()
End Sub
Function FilterArticle(ByVal a As Article) As Boolean
Return a.articleID Mod 2 = 0
End Function
End Module
then you will still get the expected SQL:
SELECT ... FROM Articles WHERE articleID > 10
The where clause that can be converted to SQL is filtered in the SELECT statement, but the clause that cannot be converted is filtered on the client side. I would have hoped that the order of the where clauses would not matter since they are just being ANDed, but that is not the case.
The lesson is that if you need to chain a where clause that cannot be converted to SQL, try to put it at the end of the chain. This can be a real issue if you are using a data access layer that automatically filters queries (eg: for security) with a function that cannot convert to SQL. If all of your LINQ to SQL queries have this built in filter then none of them will ever generate WHERE clauses in the SQL, it will just load the entire table every time.
February 4, 2010, 8:57 pm
Download the code here: WpfLoadingOverlay.zip
A common issue in MDI or TDI style user interfaces is that it can take a long time to render new forms when they are opened. Even a one or two second delay is enough to make an application seem very unresponsive. If there’s nothing you can do to improve the actual performance (ie: the total time to load the form) you can at least improve the perceived performance. This code sample creates a simple form with a tab control and a button. Every time you click the button it adds a new tab with 4000 text boxes. Depending on the speed of your computer the form will probably take around five seconds to load. As soon as you click the button, the new tab appears with a loading animation that continues until the contents have been rendered. This is not a trivial task because we want to render a loading animation at the same time that we are waiting for another long rendering operation to complete. Basically, we need two rendering threads. You can’t have multiple rendering threads in a single window, but you can put your loading animation in a new window with its own rendering thread and make it look like it’s not a separate window.
The loading overlay is a separate, chromeless window that does not appear on the taskbar and disappears as soon as loading is complete. The window is also semi-transparent and automatically positioned exactly over top of the form that is loading, so it looks like it is a part of the existing window.
Download the sample project and try it out.