Archive for the ‘VB.NET’ Category.
March 6, 2010, 9:17 am
A common pattern in database design is to use make a column required, give it a default value and then never think about it when doing INSERTs. A perfect example would be a createdDate column on the Users with a default value of GetDate(). Here’s the full table definition:
- userID (identity key)
- userName
- password
- ts (timestamp)
- createdDate (default value = GetDate())
In this case we can easily insert into the table without worrying about the createdDate, userID, or ts columns:
INSERT INTO Users (userName, password) VALUES ('asdf', 'qwer')
However, since this is the 21st century, we don’t want to do this in SQL, we want to do it with an ORM. Unfortunately, LINQ to SQL doesn’t do a very good job with this.
Using context = New TestDataContext
' Output SQL to the console for debugging
context.Log = Console.Out
' Attach a new user and submit the changes
Dim newUser As New User With {.userName = "NewUser", .password = "password"}
context.Users.InsertOnSubmit(newUser)
context.SubmitChanges()
End Using
The above code generates the following INSERT statement when SubmitChanges() is called (note: I replaced @p0, @p1, etc with their actual values to make the query more readable):
INSERT INTO [dbo].[Users]([userName], [password], [createdDate]) VALUES ('NewUser', 'password', NULL)
This query fails and we get a SqlTypeException because createdDate is NOT NULL and NULL cannot be converted to a valid date. Notice that the generated SQL does not attempt to explicitly set a value for userID or ts. It appears that LINQ to SQL knows how to deal with IDENTITY fields and TIMESTAMPs, but not how to deal with other required columns that happen to have a default value.
I would have expected LINQ to SQL to generate a query that does not explicitly set createdDate so that SQL Server could handle it, but no such luck. You can easily set the createdDate manually like this:
Dim newUser As New User With {.userName = "NewUser", .password = "password", .createdDate = Date.Now}
It really sucks to have to do this every time though, especially if you have many fields to fill in. A possible alternative is to put a partial class on either your DataContext or just on the User class and write some code that will automatically initialize fields like createdDate. If you want to make generic behaviour for this (eg: automatically set columns named “createdDate” to Date.Now when SubmitChanges is called) you can do something like this in the DataContext partial class:
Public Overrides Sub SubmitChanges(ByVal failureMode As ConflictMode)
' NOTE: this is just a sample to get you started
For Each insert In GetChangeSet().Inserts
Dim createdDateProp = insert.GetType.GetProperty("createdDate")
If createdDateProp IsNot Nothing Then
createdDateProp.SetValue(insert, Date.Now, Nothing)
End If
Next
MyBase.SubmitChanges(failureMode)
End Sub
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 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 than 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.
January 31, 2010, 8:45 pm
If you attach an entity with a required association that is nulled out, you will be unable to call GetChangeSet(). In my opinion, the expected behaviour is that the entity should show up in the change set as though it is valid, but an exception should be thrown when you attempt to call SubmitChanges() because a foreign key constraint has been violated. In fact, with code like this we will get exactly that result (an exception is thrown on SubmitChanges()):
Using testData As New TestDataContext
Dim newArticle As New Article With {.title = "Foobar", _
.text = "blah blah blah"}
testData.Articles.InsertOnSubmit(newArticle)
Dim changes = testData.GetChangeSet()
testData.SubmitChanges()
End Using
There is a required association to the Users table that has not been set at all. Using the following snippet, with the User property explicitly set to Nothing an exception will be thrown on GetChangeSet() instead of SubmitChanges():
Using testData As New TestDataContext
Dim newArticle As New Article With {.title = "Foobar", _
.text = "blah blah blah", _
.User = Nothing}
testData.Articles.InsertOnSubmit(newArticle)
Dim changes = testData.GetChangeSet()
testData.SubmitChanges()
End Using
It gives this error on GetChangeSet():
An attempt was made to remove a relationship between a User and a Article. However, one of the relationship’s foreign keys (Article.userID) cannot be set to null.
It appears that the internal implementation of LINQ to SQL distinguishes between an unset relationship, and one that has specifically been set to Nothing. The awkward thing here is that it is not always easy to avoid this issue since you don’t even have to call InsertOnSubmit. Attaching an entity by setting an association to an already attached object gives the same result.
Using testData As New TestDataContext
Dim existingUser = testData.Users.First
Dim newUserGroup As New UserGroup With {.User = existingUser, .Group = Nothing}
Dim changes = testData.GetChangeSet()
testData.SubmitChanges()
End Using
In this snippet there are two required associations: User and Group. As soon as User is set, the UserGroup entity is attached to the DataContext. However, since Group is Nothing the ChangeSet is now corrupt.
This bug is described in this forum thread where a Microsoft employee called it a bug and recommended that he post it on Connect (Microsoft’s bug tracking site). The bug report on Connect can be found here. One hour after it was posted Microsoft replied saying this:
We are currently investigating. The investigation process normally takes 7-14 days.
They then went silent for 9 months before posting this:
Hi,
Thank you for taking the time to send this feedback and bug report. We have reviewed the issue and confirmed the behavior, but we will not be fixing this in the next release of LINQ to SQL.
LINQ to SQL Team
That’s Microsoft for ya.
January 31, 2010, 4:48 pm
By default, LINQ to SQL uses deferred loading. When you want to eager load an entity’s associated data you need to set DataLoadOptions using the LoadOptions property on the DataContext. If you have a one-to-many relationship between Users and Articles you can force LINQ to SQL to eager load Articles with Users like this:
Using testData As New TestDataContext
' Log SQL queries to the console
testData.Log = Console.Out
' Set LoadOptions
Dim options As New DataLoadOptions
options.LoadWith(Function(user As User) user.Articles)
testData.LoadOptions = options
' Load users with their articles
Dim users = testData.Users.ToList
For Each user In users
Dim articles = user.Articles.ToList
Next
End Using
This will generate a single SELECT statement with a JOIN on the Articles table. The same goes for for one-to-one relationships. You can also use LoadWith as many times as you want. For one-to-one relationships and no more than a single one-to-many relationship this will still generate one query with JOINs to all the LoadWith tables. However, if you want to eager load multiple one-to-many relationships you will get into a select N + 1 situation (or worse). For example, this code eager loads Articles and UserGroups with each User entity:
Using testData As New TestDataContext
' Log SQL queries to the console
testData.Log = Console.Out
' Set LoadOptions
Dim options As New DataLoadOptions
options.LoadWith(Function(user As User) user.Articles)
options.LoadWith(Function(user As User) user.UserGroups)
testData.LoadOptions = options
' Load users with their articles
Dim users = testData.Users.ToList
For Each user In users
Dim articles = user.Articles.ToList
Dim userGroups = user.UserGroups.ToList
Next
End Using
Technically, the behaviour here is correct. It will successfully eager load both the Articles and UserGroups collections for each User, but it will not do it in a single query. When I ran this I got one query that fetched the Users and Articles like last time, but then a separate SELECT for each UserGroup rather than another JOIN. Even though this won’t alter the behaviour of the code, it will definitely make a major impact on performance, especially if there are a lot of users in the database.
Scott Guthrie confirmed this behaviour in a post on David Hayden’s blog. This is what he said:
In the case of a 1:n associations, LINQ to SQL only supports joining-in one 1:n association per query.
Lame.
January 23, 2010, 9:15 am
A couple months ago I wrote this article explaining why I think it is reasonable for unit tests to hit a real database. Subsequently, I wrote a follow up article describing some techniques for rolling back your database to its original state after each test. In that article I found that just using simple transactions did not solve the problem because you need access to all database connections being used, and they all have to be rolled back. I have since found a way around this problem using distributed transactions.
With the Microsoft Distributed Transaction Coordinator (MSDTC) the activity over multiple connections can be lumped into a single transaction using the TransactionScope class. MSDTC needs to be running for this to work, but since this is just for unit tests it doesn’t need to be enabled on your production environment.
In order to use the TransactionScope class your project will need a reference to System.Transactions. Here’s a sample unit test using MSTest and Entity Framework where the database is altered with multiple connections within a transaction and then the changes are rolled back:
Imports System.Transactions
Imports System
Imports System.Text
Imports System.Collections.Generic
Imports Microsoft.VisualStudio.TestTools.UnitTesting
<TestClass()> _
Public Class UnitTestSample
<TestMethod()> _
Public Sub ProofOfConceptTest()
Using New TransactionScope
Dim conn1 As New DataTestEntities
Dim conn2 As New DataTestEntities
Dim row1 As New Users With {.userName = "user1", .password = "pass"}
Dim row2 As New Users With {.userName = "user2", .password = "pass"}
conn1.AddToUsers(row1)
conn2.AddToUsers(row2)
conn1.SaveChanges()
conn2.SaveChanges()
Dim conn3 As New DataTestEntities
Assert.AreEqual(conn3.Users.Count, 6)
End Using
End Sub
End Class
Alternatively, if you want every test method inside a test class to be within its own TransactionScope without adding a Using block to every single test, you can use the initialization an cleanup methods like this:
Imports System.Transactions
Imports System
Imports System.Text
Imports System.Collections.Generic
Imports Microsoft.VisualStudio.TestTools.UnitTesting
<TestClass()> _
Public Class UnitTestSample
Private _transaction As TransactionScope
<TestInitialize()> _
Public Sub Setup()
_transaction = New TransactionScope
End Sub
<TestCleanup()> _
Public Sub TearDown()
_transaction.Dispose()
End Sub
<TestMethod()> _
Public Sub ProofOfConceptTest()
Dim conn1 As New DataTestEntities
Dim conn2 As New DataTestEntities
Dim row1 As New Users With {.userName = "user1", .password = "pass"}
Dim row2 As New Users With {.userName = "user2", .password = "pass"}
conn1.AddToUsers(row1)
conn2.AddToUsers(row2)
conn1.SaveChanges()
conn2.SaveChanges()
Dim conn3 As New DataTestEntities
Assert.AreEqual(conn3.Users.Count, 6)
End Sub
End Class
As long as the use of MSDTC is an option, I have found this method to be far better than any of those described in the last article. It guarantees that the state or your database is maintained and is extremely fast (at least on small amounts of data).
October 27, 2009, 7:37 pm
WPF data binding has built in support for IDataErrorInfo, so it is easy to display a validation error when a property has invalid data. However, sometimes we need to manually find all the validation errors on an object. A perfect example of this is when trying to save data. Often you will want to verify that there are no validation errors before allowing the save operation to proceed. Using the MVVM pattern it would be ideal to determine whether there are any errors purely within your model view. The default behavior of IDataErrorInfo does not give a collection of all current errors. Instead, it will just tell you if a given property has an error (ie: using IDataErrorInfo.Item), so all we have to do to find all the errors is enumerate through each property on the class and call the Item property with that property name as the argument. The GetValidationErrors function below does exactly that:
Option Strict On
Imports System.ComponentModel
Public Class ValidationHelper
''' <summary>
''' Checks for errors in <c>validatable</c> and returns all the errors found.
''' </summary>
Public Shared Function GetValidationErrors(ByVal validatable As IDataErrorInfo) As IEnumerable(Of DataError)
Dim errors As New List(Of DataError)
' Iterate through every property in the class
For Each prop In validatable.GetType.GetProperties
' If the property has an error, then add it to the list
Dim errorMessage = validatable(prop.Name)
If errorMessage IsNot Nothing Then
errors.Add(New DataError(prop.Name, errorMessage))
End If
Next
Return errors
End Function
''' <summary>
''' Represents a single error. It's a propertyName, errorMessage pair
''' </summary>
Public Class DataError
Private _propertyName As String
Private _errorMessage As String
Public Sub New(ByVal propertyName As String, ByVal errorMessage As String)
_propertyName = propertyName
_errorMessage = errorMessage
End Sub
''' <summary>
''' The name of the property that has the error.
''' </summary>
Public ReadOnly Property PropertyName() As String
Get
Return _propertyName
End Get
End Property
''' <summary>
''' A description of the property's error.
''' </summary>
Public ReadOnly Property ErrorMessage() As String
Get
Return _errorMessage
End Get
End Property
End Class
End Class
October 24, 2009, 2:43 pm
Josh Smith just made a blog post about XAML DataContext comments when using the MVVM pattern. He makes a great point, which is that in many cases it is not immediately obvious what the DataContext of a view is intended to be. A simple comment as Josh suggests will go a long way, but the downside is that comments create a maintainability issue. If you rename a model view, or refactor code so that a page, window or user control expects a different DataContext you also need to update the comment. Here’s an example:
<!-- DataContext = SampleModelView -->
<UserControl x:Class="SampleUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300">
<Grid>
</Grid>
</UserControl>
But now what if I change my mind and decide that instead of SampleModelView, this UserControl will have AlternateModelView as its DataContext? If I forget to update the comment then it is now a source of misinformation. What I would really like to do is somehow specify the expected data context type for a given UI element, so I created an attached property called ExpectedDataContextType. When the element is loaded, it will fail at runtime if the DataContext is not of the desired type. It looks like this:
<UserControl x:Class="TestControl"
xmlns:local="clr-namespace:ExpectedDataContextType"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="300" Height="300"
local:DataContextHelper.ExpectedDataContextType="{x:Type local:TestModelView}">
<Grid>
<TextBlock>Hello</TextBlock>
</Grid>
</UserControl>
The code is extremely simple. It just attaches a handler to the Loaded event and then checks the type. When the types do not match up it gives you a warning via a message box if you are debugging. I chose not to throw an exception because the exception gets covered up and just ends up as a tiny message on Visual Studio immediate window. When I am debugging and a control has the wrong DataContext I want to make sure I find out about it, hence the message box. You can always replace the message box code with something else though.
Option Strict On
Public Class DataContextHelper
Public Shared Function GetExpectedDataContextType(ByVal element As DependencyObject) As Type
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
Return DirectCast(element.GetValue(ExpectedDataContextTypeProperty), Type)
End Function
Public Shared Sub SetExpectedDataContextType(ByVal element As DependencyObject, ByVal value As Type)
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
element.SetValue(ExpectedDataContextTypeProperty, value)
End Sub
Public Shared ReadOnly ExpectedDataContextTypeProperty As _
DependencyProperty = DependencyProperty.RegisterAttached("ExpectedDataContextType", _
GetType(Type), GetType(DataContextHelper), _
New FrameworkPropertyMetadata(Nothing, AddressOf OnExpectedDataContextTypeChanged))
Private Shared Sub OnExpectedDataContextTypeChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
Dim element = DirectCast(obj, FrameworkElement)
AddHandler element.Loaded, AddressOf OnElementLoaded
End Sub
Private Shared Sub OnElementLoaded(ByVal sender As Object, ByVal args As RoutedEventArgs)
Dim element = DirectCast(sender, FrameworkElement)
RemoveHandler element.Loaded, AddressOf OnElementLoaded
' Compare the expected type to the actual type
Dim expectedDataContextType = GetExpectedDataContextType(element)
Dim actualDataContextType = element.DataContext.GetType
If expectedDataContextType IsNot actualDataContextType Then
#If DEBUG Then
' The types don't match and debug mode is on so notify the developer that the element
' has the wrong data context
MessageBox.Show(String.Format("DataContext has type {0}. Expected {1}.", _
actualDataContextType.ToString, _
expectedDataContextType.ToString))
#End If
End If
End Sub
End Class
The end result is that you are still specifying what the type of your DataContext should be, but now it is actually enforced. The best part is that if the you rename your model view without updating the ExpectedDataContextType property you will get a compile error because the type no longer exists. If the type still exists then you have to settle for a runtime error.
October 17, 2009, 11:16 am
As almost anyone who tries to unit test a database application will quickly discover, databases present a huge problem for unit testing. Strictly speaking, if you are testing your C# or VB code and you actually hit a real database, then it isn’t really a unit test. It is actually an integration test. However, I have found that it doesn’t really matter what you call it, the end result is that your tests are much more useful if they actually hit a real database. You don’t have to worry about whether the test failed because you screwed up your mock object or if the actual application is buggy and you get better code coverage because even broken SQL will lead to a failed test.
There are several methods that can be used to prevent your unit tests from actually using a real SQL Server database, but they all have their problems:
- Using an in-memory provider like SQLite
There is an Entity Framework provider for SQLite that allows you to interact with a database without using a network or even going to your file system. This could certainly increase the execution speed of your unit tests and makes it easy to prevent cross contamination in your tests, but they are still integration tests. The only difference is that you are now testing whether your code works on SQLite, rather than the DBMS that you will actually use in production. The problem is that all database systems have different behaviors and feature sets, so your tests are no longer valid if you use a different DBMS for testing. There is also currently no system in place to automatically generate the SQLite schema from your entity data model, so you will need to find your own way of doing that, or you have to manually maintain a separate SQLite schema. Gross. If you are going to use another provider, it needs to be specially designed to behave exactly like your production database (ie: a mock SQL Server provider) but to my knowledge no such providers exists (if I’m wrong, please let me know!).
- Mock the Entity Framework ObjectContext
If all you want to do is read data, then this works well and is easy to implement. Unfortunately, in the vast majority of cases, we also need to write data and that’s where this method gets tricky. Your mock ObjectContext needs to be able to track changes and save them to an in-memory repository. And again, you have to make sure that it behaves exactly like your production database. Because this method often involves either a huge wrapper or major alterations to auto-generated code (which means you also need to make your own generator or you’ll lose maintainability) the mock object itself is extremely complicated, leaving a high likelihood that it will have errors. Since the mock is so complicated one could argue that you are again doing integration tests, not unit tests. But this time instead of testing your code and the database, you are testing your code and the mock ObjectContext. Just like the SQLite example, this is much worse because you are testing whether your code integrates with something you will not use in production. If you are going to do integration tests anyways, then you might as well integrate with the real thing. This method could lead to faster executing tests, but don’t forget that a local SQL Server instance is actually extremely fast and might be just as good.
- Encapsulate your data access layer and then mock it
I see this response on message boards all the time. Whenever someone asks how they unit test their data access code someone will respond “You’re doing it wrong, put all of your data access code into a separate module that you can mock”. There are a couple problems with this. First of all, you still need to test the code in the data access layer. If you have a function in your DAL that executes a complicated LINQ to Entites query, then you want to test that query. Without using one of the techniques mentioned above, this requires hitting the database. Secondly, making your client code completely unaware of the data access layer’s implementation leads to some issues. Let’s pretend that my data access layer looks like this:
Public Interface IUsersModel
Function GetUsers() As IEnumerable(Of Users)
Sub Save()
End Interface
Public Class UsersModel
Implements IUsersModel
Private _context As New DataTestEntities
Public Function GetUsers() As IEnumerable(Of Users) Implements IUsersModel.GetUsers
Return _context.Users
End Function
Public Sub Save() Implements IUsersModel.Save
_context.SaveChanges()
End Sub
End Class
It’s pretty simple, the code just allows you to get a collection of users and save any changes you make. UsersModel correctly implements the interface using the Entity Framework. Then we also have a controller that accesses the DAL. It looks like this:
Public Class UsersController
Private _usersModel As IUsersModel
Public Sub New(ByVal usersModel As IUsersModel)
_usersModel = usersModel
End Sub
Public Sub ChangeFirstUserNameToFoobar()
_usersModel.GetUsers().First.userName = "foobar"
_usersModel.Save()
End Sub
End Class
UsersController has a dependency on IUsersModel, so when unit testing the ChangeFirstUserNameToFoobar method, we pass in a mock implementation of IUsersModel, but we cannot simply verify that Save() was called, we also need to know what is going to happen when Save is called. Specifically, we need some way of checking that the first user’s username was changed to “foobar”. This means that a mocking framework like RhinoMocks or Moq will not be sufficient. There must be a fake implementation of IUsersModel that keeps track of the changes that have been made. Now we are getting back into “mock the ObjectContext” territory because that’s basically what we will have done.
There is a definite trend here: each of the above methods is complicated enough that you lose the benefits of isolating your tests from the database. They are all integration tests. In every case you are testing your client code, plus the repository. Since you have to test a repository, it might as well be the real one. Of course, this presents its own challenges. You will want to use a local instance of SQL Server (or whatever DBMS you use) to keep the tests fast (and isolated from other developers) and you will need to roll back changes after each test. In subsequent articles I will look at how to deal with these issues.
Update: I have posted the second article: Unit testing an Enitity Framework DAL part 2: Rolling back the test database