Logo




Subscribe:
RSS 2.0 | Atom 1.0
Categories:

Sign In


[Giagnocavo]Michael::Write()

# Sunday, August 26, 2007
A LINQ to the CRUD

A question many people have run into is: How does CRUD fit into LINQ-to-SQL? While LINQ-to-SQL (I'll abbreviate as DLINQ) provides a very fast and easy way for us to start querying our data, it doesn't handle updates as beautifully. It is particularly noticeable when you are doing multi-tier and hence cannot share a DataContext.

Let's consider an example database called "MembersDatabase" which has a table called "Accounts". Accounts has an int primary key, and a varchar Email field. We use the DLINQ designer and create the dbml that generates a class called MembersDataContext. How does our app-tier code look? I'll give you a hint, it starts with "ug" and rhymes with "nasty":

// Select
int someId = 123; // Passed from another tier
Account someAccount; // Can't use implicit typing -- no anonymous types
using (var dc = new MembersDataContext()) {
    someAccount = dc.Accounts.SingleOrDefault(a => a.AccountId == someId);
}

// Insert
var myAccount = new Account();
myAccount.Email = "me@contoso.com";
using (var dc = new MembersDataContext()) {
    dc.Accounts.Add(myAccount); 
    dc.SubmitChanges();
}

// Update
int myId = 1; // Id and email passed from another tier
string newEmail = "cool@cool.com";
var changedAccount = new Account();
changedAccount.AccountId = myId;
changedAccount.Email = newEmail;
using (var dc = new MembersDataContext()) {
    dc.Accounts.Attach(changedAccount, true); 
    dc.SubmitChanges();
}

// Delete
int idToKill = 2; // Passed from another tier
using (var dc = new MembersDataContext()) {
    using (var txScope = new System.Transactions.TransactionScope()) {
        var acc = dc.Accounts.SingleOrDefault(a => a.AccountId == idToKill); 
        if (acc == null) throw new ChangeConflictException("Row not found."); 
        dc.Accounts.Remove(acc); 
        dc.SubmitChanges();
        txScope.Complete();
    }
}

It's not horrible; it's certainly better than anything before it. But, we can do better. I created some helper classes to do so (see attached file).

First, DatabaseBase<TContext>. This holds our tables, and provides DataContext helper functions Use and Query. Second, TableBase<TItem, TKey>. This actually provides our CRUD methods. I don't think anyone is overly interested in the implementation details (comment if I'm wrong), so here's how you declare your CRUD types:

class MembersDatabase : DatabaseBase<MembersDataContext>
{
    public static readonly TableBase<Account, int> Accounts 
        = CreateTable(dc => dc.Accounts, a => a.AccountId);
}

That's it.

You create a new class to serve as your "database" class. All that's required here is to inherit from DatabaseBase.

Next, for each table, simply create a new field via CreateTable. The first parameter is a lambda function that selects the right table off the DataContext. The second parameter is a lambda expression that selects the primary key. Not much too it.

So, how does our previous chunk of code look with this small helper library?

// Select
int someId = 123; // Passed from another tier
var someAccount = MembersDatabase.Accounts.SelectByKey(someId);

// Insert
var myAccount = new Account();
myAccount.Email = "me@contoso.com";
MembersDatabase.Accounts.Insert(myAccount);

// Update
int myId = 1; // Id and email passed from another tier
string newEmail = "me@me.com";
var changedAccount = new Account();
changedAccount.AccountId = myId;
changedAccount.Email = newEmail;
MembersDatabase.Accounts.Update(changedAccount);

// Delete
int idToKill = 2; // Passed from another tier
MembersDatabase.Accounts.Delete(idToKill);

That's about 40% less code. It’s far more straightforward, being a single block.

To start using this code, just drop DatabaseBase.cs into your project. It adds DatabaseBase to System.Data.Linq. Then subclass as shown above, and you're on your way to LINQ updating bliss. What do you think?

DatabaseBase.cs (5.65 KB)

P.S. At any rate, I should get points for the Zelda pun, right?

Update: I forgot to mention, you'll want to turn UpdateCheck to Never on your columns in the LINQ-to-SQL designer.
Update: The code (including the Tuple class) for the .NET 3.5 RTM release is here: http://www.atrevido.net/blog/2008/06/26/LINQ+To+The+CRUD+RTM.aspx

Code
Sunday, August 26, 2007 8:30:32 PM UTC  #    Comments [10]  |  Trackback

Saturday, September 01, 2007 2:39:19 AM UTC
Hi, nice artcle, thanks. I'm in a vb shop and would love it if you could help me with one small part of your implementation:

public TItem Insert(TItem item)
{
return DatabaseBase<TContext>.Query(dc =>
{
tableSelector(dc).Add(item);
dc.SubmitChanges();
return item;
});
}

Not sure how this converts to VB.Net, I figures out the new Function call but not how to include method calls from within them.

Thanks in advance.
Monday, September 03, 2007 2:01:29 AM UTC
Hmm, it seems as if VB doesn't support lambda functions, just simple expressions? That really would suck... but it's not surprising if it's true.
Thursday, September 06, 2007 2:52:49 PM UTC
Just a naming suggestion, but *please* do not use "Base" in your type names. Use Database<TContext> and Table<TItem, TKey> instead.

See also: Framework Design Guidelines, Section 6.2 "Base Classes":

"Avoid naming base classes with a "Base" suffix if the class is intended for use in public APIs.

"For example, despite the fact that Collection<T> is designed to be inherited from, in many cases frameworks expose APIs typed as the base class, not as its subclasses, mainly because of the cost associated with a new public type.

public Directory {
public Collection<string> GetFilenames () {
return new FilenameCollection(this);
}

private class FilenameCollection : Collection<string> {...}
}

"The fact that Collection<T> is a base class is irrelevant for the user of the GetFilename method, so the "Base" suffix would only create an unneccesasary distraction for the user of the method."
Thursday, September 06, 2007 3:07:17 PM UTC
Jonathan, You're absolutely right. I dislike those names. I didn't want to use Table since it's already a class (although, not with two generic parameters). But yes, the names could definately be fixed up.
Saturday, September 08, 2007 8:21:28 AM UTC
Michael, I'm doing something very similar. Instead of extending the LINQ generated classes or their bases I'm wrapping with a business object instead:

public class BusinessBase<TEntity, TContext>

and

public class busCustomer<CustomerEntity,TimeTrakkerContext>

The business object holds the context and optionally manages disconnected operation so you can do either of these:

busCustomer Customer = new busCustomer();

CustomerEntity cust = Customer.Load(1);

... make changes

Customer.Save(); && Connected with SubmitChanges

or

Customer.Save(cust); && disconnected via .Attach

In many situations the connected approach actually works just fine and it is more efficient because it batches and there's no extra code that has to reattach entities behind the scenes.

I've found this works really well and in my case at least doesn't add any complexities since I already use an separate business layer anyway to remove all data access/LINQ data code from the front end anyway. Haven't had enough time using this stuff yet though to see all possible pitfalls though...

BTW, I had a real hard time getting this post to post. In fact, first try wiped out my post, the second try failed with missing postback options (I hit the back key after the first failure).
Saturday, September 08, 2007 8:27:07 AM UTC
Hi Rick,

Yea classes are a "stronger" way of approaching it. I have an alternative design that uses that approach. The main reason I avoided it was that it seemed like more code: subclassing, overriding, etc.

How do you create the Load and Delete functions?

I've noticed dasBlog has issues from time to time... sorry about that :\
Thursday, May 15, 2008 6:24:32 AM UTC
Hmm. New to LINQ. Included your sample class, but it does not compile. "DataContext" could not be found...

Any hint?
Thursday, June 26, 2008 8:18:55 PM UTC
Neil, try adding a reference to system.data.linq

Of course, now it complains:

Error 1 The type or namespace name 'Tuple' could not be found (are you missing a using directive or an assembly reference?) C:\Projects\cConverter\cConverter\database.cs 235 44 cConverter

And that is stumping me.
bob
Thursday, June 26, 2008 8:28:19 PM UTC
Bob, Niel, please see the updated version for .NET 3.5 RTM here: http://www.atrevido.net/blog/2008/06/26/LINQ+To+The+CRUD+RTM.aspx

I have the Tuple class (since the .NET BCL doesn't ship with one) posted there.
Wednesday, February 25, 2009 12:59:27 PM UTC
Hi,

Where is the class TableBase<TItem, TKey>?

Best,

Jamal
OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Live Comment Preview