Wednesday, April 2, 2008

Linq, DataContext, and Transactions..Part 2

I decided to add the code and rest of my comments on CodeProject.
I will be keeping the project updated as I refine the class.

The class now opens connections / transactions only when needed.

GREAT SUCCESS!

Wednesday, March 26, 2008

A Generic Session Wrapper for 2.0/3.5

Just something I use quite often for my Web applications and decided to throw out there.

I often write a Facade class that exposes Session Variables using properties, but under the covers, this class is called.

It could use a little updating, but it works. *shrug*

public static partial class SessionHelper
{
static SessionHelper()
{
}

static public T Read(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException("name");
}
if (HttpContext.Current == null) return default(T);
if (HttpContext.Current.Session[name] != null)
{
return (T)HttpContext.Current.Session[name];
}
return default(T);
}
static public T Read(int index)
{
if (HttpContext.Current == null) return default(T);
if (HttpContext.Current.Session[index] != null)
{
return (T)HttpContext.Current.Session[index];
}
return default(T);

}

static public void Remove(string name)
{
if (HttpContext.Current == null) return;
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException("name");
}
HttpContext.Current.Session.Remove(name);
}

static public void Store(string name, object item)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException("name");
}
HttpContext.Current.Session.Add(name, item);
}

}

Linq, DataContext, and Transactions..Part 1

Linq to Sql is a cruel mistress.

The groans about Linq to Sql and N-tier development are echoing all over the internet. Rather than recant what has already been stated (by some very knowledgeable people), I will take on another portion of the Linq to Sql framework, the DataContext.

Transactions & the DataContext often cause headaches, but they can be resolved with a simple wrapper class.

Before we begin, I will make a few points regarding Transactions & the DataContext:

LINQ to SQL supports three distinct transaction models. The following lists these models in the order of checks performed.
- Explicit Local Transaction

When SubmitChanges is called, if the Transaction property is set to a (IDbTransaction) transaction, the SubmitChanges call is executed in the context of the same transaction.

It is your responsibility to commit or rollback the transaction after successful execution of the transaction. The connection corresponding to the transaction must match the connection used for constructing the DataContext. An exception is thrown if a different connection is used.

- Explicit Distributable Transaction

You can call LINQ to SQL APIs (including but not limited to SubmitChanges) in the scope of an active Transaction. LINQ to SQL detects that the call is in the scope of a transaction and does not create a new transaction. LINQ to SQL also avoids closing the connection in this case. You can perform query and SubmitChanges executions in the context of such a transaction.

- Implicit Transaction

When you call SubmitChanges, LINQ to SQL checks to see whether the call is in the scope of a Transaction or if the Transaction property (IDbTransaction) is set to a user-started local transaction. If it finds neither transaction, LINQ to SQL starts a local transaction (IDbTransaction) and uses it to execute the generated SQL commands. When all SQL commands have been successfully completed, LINQ to SQL commits the local transaction and returns.

In summary:

1.) Linq creates a Transaction for you when calling SubmitChanges. What if you need to change the transaction isolation level? What if you need to execute a query with a (NOLOCK) clause? (ReadUncommitted) How can you accomplish this task using the default DataContext? It can be done, but we want a reusable solution.

2.) Using System.Transactions is an elegant approach and the ONLY option for distributed transactions. If you opt to wrap all transactions in a System.Transactions block, then you might not need a solution for DataContext Transactions.

3.) If you manually start a Transaction, YOU must close it. This holds true for connections as well. This is an important point to remember when creating this class.

Now we need a class that handles all of opening/closing of transactions and facilitates setting transaction levels in your DataContext.

On To Part 2!

Extending Linq To Sql Entity Classes

A common question regarding the Linq to Sql is how to extend the base entities created by the Linq to Sql generator.

When you create a class using the DBML designer (yuck!) you get a class with 10,000 lines of code hiding under a designer.cs file.


Avoid mucking with this designer file at all costs! Any changes to your schema will regenerate the classes in this file and wipe out all of your changes.

So how do you extend it?

Using partial classes.

When working with automatically generated source, code can be added to the class without having to recreate the source file. Visual Studio uses this approach when creating Windows Forms, Web Service wrapper code, and so on. You can create code that uses these classes without having to edit the file created by Visual Studio.
Here is a sample class definition created by the MSLinqToSQLGenerator.

public partial class Customer: INotifyPropertyChanging, INotifyPropertyChanged
{

}

In another class file you can extend this partial class as needed. I always recommend creating a new class file to hold your partial class.

public partial class Customer
{

}

Your external class can implement interfaces cleanly.

public interface IEntityObject
{
bool IsDirty();
bool IsNew();
}

public partial class Customer: IEntityObject
{
#region IEntityObject Members

public bool IsDirty()
{
throw new NotImplementedException();
}

public new bool IsNew()
{
throw new NotImplementedException();
}

#endregion
}

Casting the Customer object to an IEntityObject is now valid.

You can also inherit from a base class.

public abstract class EntityBase
{
public abstract void Validate();
}
public partial class Customer : EntityBase
{
public override void Validate()
{
throw new NotImplementedException();
}
}

Done.