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 30, 2010, 9:04 pm
There is a really annoying bug (or at least what I would call a bug) in SQL Server Management Studio where you cannot login with a user whose default database does not exist. Even if you are already logged in and you rename the default database, you will automatically be logged out and will receive an error every time you try to login again. You can always login as a different user and change the default database, but if you only have access to the one account, you can change the default database using sqlcmd.
First, login with a different database in a command window:
sqlcmd -d master -S server -U username -P password
Then issue the following commands to change the default database:
1> ALTER LOGIN login_name WITH DEFAULT_DATABASE = master
2> GO
You should now be able to login with this account through management studio.
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 and 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).