Wednesday, July 30, 2008

Common Base Class for LINQ to SQL

Download Source - 126kb

Language-Integrated Query (LINQ) is a set of features in Visual Studio 2008 that extends powerful query capabilities to the language syntax of C# and Visual Basic. As a part of LINQ, LINQ to SQL provides a run-time architecture for managing relational data as objects. To some extent, it equals to ORM tool or framework such as NHibernate and Castle based on .NET framework. It becomes our preferred choice gradually when we want to access the database.

In LINQ to SQL, all variables in the Data Model of a relational database can be strongly typed which provides the benefit of compile-time validation and IntelliSense. We can fetch the data from the database using query expression (it includes query syntax and method syntax.)

However, the strongly typed feature is not conducive to abstract the common logic of data operations so the developer has to define a specific class to handle the entity object. It results in a large number of repeated codes. If we can implement the base class which encapsulates the common operations such as Select, Where, Add, Update and Delete, it will be useful for N-Tier application.

Fortunately, the generic type will help us to achieve our goal. We can invoke a method called GetTable() in DataContext of LINQ. For example, we can implement the Where method which accepts the Lambda expression to find the result we want to get:

public IList Where(Func predicate)
{
InitDataContext();
return m_context.GetTable().Where(predicate).ToList();
}


It’s simple. Even, we can expose the method which accepts the condition clause using Dynamic Query:

public static class DynamicQueryable
{
public static IQueryable Where(this IQueryable source, string predicate, params object[] values)
{
return (IQueryable)Where((IQueryable)source, predicate, values);
}

public static IQueryable Where(this IQueryable source, string predicate, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (predicate == null) throw new ArgumentNullException("predicate");
LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, values);
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "Where",
new Type[] { source.ElementType },
source.Expression, Expression.Quote(lambda)));
}
}
public IList Where(string predicate, params object[] values)
{
InitDataContext();
return m_context.GetTable().Where(predicate, values).ToList();
}


Of course, the query function is not a problem because we don’t need the properties of the entity when we invoke these methods and don’t care about the composition of Lambda Expression.

The key issue is how to update or delete the record of data table. You know, we must fetch the entity which is going to be operated before updating or deleting it. The keyword to search is often its identity. Furthermore, Object Identity and Change Tracking in Data Context need the object’s identity to track the changing of it. Usually, we would add the Id column which is identity or Guid for each table. Then we can fetch the entity object according to it:

public void Update(Employee employee)
{
LinqSampleDataContext context = new LinqSampleDataContext();

Employee emp = this.Where(e => e.EmployeeID == employee. EmployeeID);
emp.FirstName = "First Name";
emp.LastName = "Last Name";

context.SubmitChanges();
}


Obviously, we don’t know the entity’s properties if we use the generic type such as TEntity like above. Without the keyword how do we associate the changes with the existing record? LINQ introduces the Attach method which can attach the changed object to the context. There are three overloaded versions as below:
Attach(Object entity): Attaches an entity to the DataContext in an unmodified state;
Attach(Object entity, bool asModified): Attaches all entities of a collection to the DataContext in either a modified or unmodified state.
Attach(Object entity, Object orginal): Attaches an entity to the DataContext in either a modified or unmodified state by specifying both the entity and its original state.

Attach method is used to associate the deserialized entities to a new instance of a DataContext. However, we can associate the entity in one DataContext to another DataContext by using it. In update or delete case, this method is very useful. For example, we want to update the record according to a changed entity from another DataContext:

public void Update(TEntity changedEntity)
{
InitDataContext();

try
{
m_context.GetTable().Attach(changedEntity, true);
m_context.SubmitChanges();
}
catch (ChangeConflictException)
{
m_context.ChangeConflicts.ResolveAll(RefreshMode.KeepCurrentValues);
m_context.SubmitChanges();
}
}


It looks good, but not enough. We can’t attach a changed entity unless the entity has a TimeStamp column in the corresponding table. Therefore, we should add a column whose type is TimeStamp in SQL Server 2005, or set the IsVersion property to true for Id property in LINQ to SQL Designer. My advice is to create a TimeStamp column for your datatable, and it will improve the performance because it won’t check all columns whether they are changed during handling concurrency.

In fact, we can pass the original entity with generic type also. The only problem is how to pass the values you want to change. The solution to this problem is using Action delegate. The code snippet is as below:

public void Update(TEntity originalEntity, Action update)
{
InitDataContext();
try
{
m_context.GetTable().Attach(originalEntity);
update(originalEntity);

m_context.SubmitChanges();
}
catch (ChangeConflictException)
{
m_context.ChangeConflicts.ResolveAll(RefreshMode.KeepCurrentValues);
m_context.SubmitChanges();
}
}


Now we can invoke it passing Lambda expression like this:

[TestMethod()]
public void UpdateWithAction()
{
LinqSampleDataContext context = new LinqSampleDataContext ();
EmployeeAccessor accessor = new EmployeeAccessor();

Employee employee = context.Employees.Single(e => e.EmployeeID == 1);

accessor.Update(employee, t => { t.FirstName = "First"; t.LastName = "Last"; });
}


Unfortunately, the test case can’t pass sometimes. It will throw a NotSupportedException. And the message of exception is:
An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported.

Why? What happens? The real reason is the entity we handle has some associations with other entities. Please see figure 1:
Figure 1: The entity with association

If you remove all relationships in Employee table, and re-generated the data model, the test case would pass.

What shall we do? Of course, to remove all relationships of table explicitly is not a good way to solve the issue. It will impact on the whole data model and can’t be accepted when we develop the application. I found one guy, Steve Michelotti, raised one solution to solve it. That was to provide one method called Detach to remove the relationships using partial class:

public partial class Contact
{
public void Detach()
{
foreach (Address address in this.Addresses)
{
address.Detach();
}
}
}

public partial class Address
{
public void Detach()
{
this._AddressType = default(EntityRef);
this._State = default(EntityRef);
}
}


Good job! But it is not perfect. First, it’s too complex because we must define the Detach method for every entity which has association with others. Second, we can’t abstract it to the base class in this way. In the base class, we don’t know the specific type of TEntity. In this case, we should turn to the reflection technology. It’s my way:

private void Detach(TEntity entity)
{
foreach (FieldInfo fi in entity.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
{
if (fi.FieldType.ToString().Contains("EntityRef"))
{
var value = fi.GetValue(entity);
if (value != null)
{
fi.SetValue(entity, null);
}
}
if (fi.FieldType.ToString().Contains("EntitySet"))
{
var value = fi.GetValue(entity);
if (value != null)
{
MethodInfo mi = value.GetType().GetMethod("Clear");
if (mi != null)
{
mi.Invoke(value, null);
}

fi.SetValue(entity, value);
}
}
}
}


For EntityRef fields, we may set their values to null by calling the SetValue of FieldInfo class to remove the relationship. However, we can’t do EntitySet in the same way because it is a collection. If set to null, it will throw exception. So I get the method information of the field and invoke the Clear method to clear all items in this collection. Finally, my implementation of Update method is as below:

public void Update(TEntity originalEntity, Action update, bool hasRelationship)
{
InitDataContext();
try
{
if (hasRelationship)
{
//Remove the relationship between the entitis
Detach(originalEntity);
}
m_context.GetTable().Attach(originalEntity);
update(originalEntity);

m_context.SubmitChanges();
}
catch (ChangeConflictException)
{
m_context.ChangeConflicts.ResolveAll(RefreshMode.KeepCurrentValues);
m_context.SubmitChanges();
}
}


Delete operation is similar except we don’t need invoke the second version of Attach (Attach(object entity, bool asModified)). Here is the code snippet:

public void Delete(TEntity entity, bool hasRelationship)
{
InitDataContext();
try
{
if (hasRelationship)
{
//Remove the relationship between the entities;
Detach(entity);
}

m_context.GetTable().Attach(entity);
m_context.GetTable().DeleteOnSubmit(entity);
m_context.SubmitChanges();
}
catch (ChangeConflictException)
{
m_context.ChangeConflicts.ResolveAll(RefreshMode.KeepCurrentValues);
m_context.SubmitChanges();
}
}
public void Delete(IList entities, bool hasRelationship)
{
InitDataContext();
try
{
if (hasRelationship)
{
//Remove the relationship
foreach (TEntity entity in entities)
{
Detach(entity);
}
}

m_context.GetTable().AttachAll(entities);
m_context.GetTable().DeleteAllOnSubmit(entities);

m_context.SubmitChanges();
}
catch (ChangeConflictException)
{
m_context.ChangeConflicts.ResolveAll(RefreshMode.KeepCurrentValues);
m_context.SubmitChanges();
}
}


Don’t worry about the correctness of final result when we remove the relationship between the entities. The Attach method is just responsible for associating the entity to a new instance of DataContext to track the change. When you submit the changes, DataContext will check the real value in the mapping database and update or delete the record according to the passed entity. Especially, you should take an action such as Cascade in the database if you want to cascade delete between foreign key table and primary key table, see figure 2.
Figure 2: Set the delete rule

If no action, the System.Data.SqlClient.SqlException will be thrown and the message of it when you delete the entity as Primary Key table, like this:
The DELETE statement conflicted with the REFERENCE constraint "FK_Orders_Employees". The conflict occurred in database "Northwind", table "dbo.Orders", column 'EmployeeID'.

Maybe you notice the InitDataContext method is invoked in all methods to access the data. Its implementation like this:

private TContext m_context = null;

private TContext CreateContext()
{
return Activator.CreateInstance() as TContext;
}
private void InitDataContext()
{
m_context = CreateContext();
}


Why do we need create a new instance of DataContext for each method? The reason is Caching policy in DataContext. If you create a new instance of DataContext and query the data from the database though by it, then change its value and execute the next query though by the same instance, the DataContext will return the data stored in the internal cache rather than remapping the row to the table. For more information, please refer to the Linq in Action.

So, the best practice is to create a new instance of DataContext for each operation. Don’t worry about the performance, the DataContext is lightweight resource.

Let’s consider about the concurrency issue. The default option is Optimistic Concurrency. When the values are to be saved, the DataContext would check the previous values to see if they have been changed. If conflict occurs, the DataContext would need to know whether to automatically overwrite the previous changes, keep the previous changes, or somehow merge the changes.

The concurrency issue is not within the scope of this article. We can’t say which one of three ways is the best or worst, it depends on the business situation. Usually, I will handle concurrency by the rule of last submit win. So I encapsulate the SubmitChanges method and define it as visual method, if necessary, the subclass may override it:

public class AccessorBase
where TEntity : class, new()
where TContext : DataContext, new()
{
private TContext m_context = null;

protected virtual void SubmitChanges(TContext context)
{
try
{
context.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
context.ChangeConflicts.ResolveAll(RefreshMode.KeepCurrentValues);
context.SubmitChanges();
}
catch (Exception ex)
{
throw ex;
}
}
public void Update(TEntity originalEntity, Action update, bool hasRelationship)
{
InitDataContext();
try
{
if (hasRelationship)
{
//Remove the relationship between the entitis
Detach(originalEntity);
}

m_context.GetTable().Attach(originalEntity);

update(originalEntity);

SubmitChanges(m_context);
}
catch (InvalidCastException ex)
{
throw ex;
}
catch (NotSupportedException ex)
{
throw ex;
}
catch (Exception ex)
{
throw ex;
}
}
}


Now, we have a common base class which can be derived to handle the entity. For example:

public class EmployeeAccessor:AccessorBase
{
}


You don’t need implement any method. It’s more convenient to access the Employee table by using an instance of EmployeeAccessor:

[TestMethod()]
public void UpdateEmployee()
{
EmployeeAccessor accessor = new EmployeeAccessor();
IList entities = accessor.Where(e => e.EmployeeID == 1);

if (entities != null && entities.Count > 0)
{
entities[0].FirstName = "Bruce";
entities[0].LastName = "Zhang";

accessor.Update(entities[0],true,true);
}
}


You may let Employee entity derive my base class directly even:

public partial class Employee:AccessorBase
{
}


Its behavior is very similar with Rich Domain Model like Martin Fowler said in the article which title is Anemic Domain Model.

19 comments:

Deniz Altintas said...

Great article!

Bruce Zhang said...

Thanks.

AirMaro said...

Nice article. Do you have any examples of how to use the DynamicQueryable class?

chengja said...

Alex Rodriguez,the playerwow goldwho would restore integrity to baseball's homewow goldrunre cord,admittedwow goldMonday towow goldusing performance-enhancingwow goldwow golddrugs himself.

chengja said...

The All-Star thirddofus kamasdofus kamasbaseman said in an interviewkamas dofusacheter dofuswith ESPN that he used steroids with the Texas Rangers for three years,from 2001-03,in an attemptbuy kamasacheter kamasto justify his status as the game's highest-paid player after signing a 10-year.

chengja said...

Back then it wasworld of warcraft golda different culture,Rodriguezcheap wow goldsaid.It waswow orvery loose.I was young.I waswow power levelingstupid.

chengja said...

He said he quitbuy wow goldcheap wow goldafter 2003,hiswow power levelingdofus kamasfirst of three AL MVP seasons,because I've provedbuy ffxi gilLord of the Rings Online goldto myself and to everyone that I don't need any of that.

chengja said...

When I arrivedwow goldin Texas in 2001,Iwow goldfelt an enormous amount of pressure.I feltwow goldwow goldlike I had all the weightwow goldof the world on top of me and I needed to perform.

Anonymous said...

I think many players need the ro zeny, so more and more people beginning to buy ro zeny, but we can not buy the cheap ro zeny, this problem for us is very hard, so I hope who can tell me buy the ragnarok zeny.

I want to tell you, I know a website sell the gaia gold, and in this website all gaia online gold is very cheap, there had many customer, if you want to buy gaia gold, I suggest you to come here then to buy the cheap gaia gold.

huangtiao said...

Runes of Magic is a Massively Multiplayer Online Role playing Game without monthly costs all kinds of Rom Gold and free to download. The premise is that you have played this game and understand the necessary Runes of Magic Gold in the Runes of Magic game. Perhaps the first monster I saw taken down in the demo can gain those varieties of Runes of Magic money. Characters choose one class; the important one is that we have to buy Rom Gold first. You can switch freely between the two classes, but you can only use primary abilities from your primary class, along with cheap Runes of Magic Gold, secondary abilities from your secondary class.
It is easy to control and pick up more all kinds of latale online gold and weapons or equipment which were dropped by those monsters. You will experience a wide range of attack and strikingly gorgeous combat effects with your own latale gold. You can also freely customize your character by equipping cool armors and weapons which those you must first have need to buy latale online gold and then you can have them. There is no doubt La Tale and its very cheap latale gold will attract female gamer and as well as young gamer eyeballs. You see my problem is that I do not what class is good or fun to play without any more necessary latale money.

said...

You know ,I have some flyff penya , and my friend also has some
flyff money, do you kouw they have the same meaning, Both of them can be called
flyff gold,I just want to buy some
buy flyff penya, because there are many
cheap penya.
You know ,I have some
maple mesos, and my friend also has some
mesos, do you kouw they have the same meaning, they are called
maple story mesos, I just want to buy
maplestory mesos, because there are many
cheap mesos

Anonymous said...

As a new player , you may need some game guides or information to enhance yourself.
requiem gold is one of the hardest theme for every class at the beginning . You must have a good way to manage your requiem lant.If yor are a lucky guy ,you can earn so many requiem money by yourself . But if you are a not , I just find a nice way to get cheap requiem lant. If you need , you can buy requiem online gold at our website . Go to the related page and check the detailed information . Once you have any question , you can connect our customer service at any time .

Making knight gold is the old question : Honestly there is no fast way to make lots of knight noah . Sadly enough a lot of the people that all of a sudden come to with millions of knight online gold almost overnight probably duped . Although there are a lot of ways to make lots of knight online noah here I will tell you all of the ways that I know and what I do to buycheap knight gold.

lanzi said...

I can getmaple mesoscheaply,
Yesterday i boughtmesos for my brother.
i hope him like it. i will give maplestory mesos to him
as birthday present. i like the cheap mesos very much.
I usually buymaple story mesos and keep it in my store.

Buy metin2 gold

i can get Metin2 gold cheaply,
Yesterday i bought Metin2 yang for my brother.
i hope him like it. i willBuy metin2 gold to him
as birthday present. i like the Cheap metin2 yang very much.
I usually buy the Cheap metin2 gold and keep it in my store.

haiyan said...

runescape gold of RS Online Game, we have made several changes to Fun Orb. If you wish to buy runescape to explore other spell books, you should subscribe as a Fun Orb member. Shattered Plans - a galaxy-spanning strategy epic that use rs gold to allow up to six players to battle for supremacy. We only plan to pay cheap rs gold when we feel you will appreciate the updates. And we certainly feel that these changes and some runescape money are worth telling you about.
rohan crone has many ways for us to use. When you start the Rohan Online game, your character will be level 1. I remembered that when I started playing this Rohan game with some little cheap rohan money. My friends all told me that the best way to spend rohan online gold is a good way. But I could not like spending my own rohan online crone. If you do not like upgrading level step by step, you can cost rohan gold to help your character to reach level high.

Anonymous said...

latale online gold in the game which movement is much like an arcade game. We know, the latale gold can exchange the real money of the reality. The world you are enjoying in latale money can entertainment and get to know new friends. This economic structure lead buy latale online gold has one kinds of real value in the real world. You can use cheap latale gold to purchase various items of course, there are need to change.
The 12sky2 Gold should be a timely evolution of the world. Please step up efforts to investigate all types of twelve sky2 Gold can be set up in the game. twelvesky2 Gold to add more rudimentary fashion. The buy 12sky2 Gold can help you combat equipment of the production. Use the cheap twelve sky2 Gold may change the door to door battle for offensive and defensive resident.

yanhui said...

I did not know how to get strong, someone told me that you must have last chaos gold. He gave me some lastchaos gold, he said that I could buy last chaos gold, but I did not have money, then I played it all my spare time. From then on, I got some lastchaos money, if I did not continue to play it, I can sell cheap lastchaos gold to anyone who want.
Once I played 4story, I did not know how to get strong, someone told me that you must have 4story Luna. He gave me some 4story Gold, he said that I could buy 4story Luna, but I did not have money, then I played it all my spare time. From then on, I got some 4story money, if I did not continue to play it, I can sell cheap 4story Luna to anyone who want.

Anonymous said...

Do you know the Archlord gold, in the game you need the
Archlord money. it can help you increase your level. My friends always asked me how to
buy Archlord gold, and I do not know he spend how much money to buy the
archlord online Gold, when I see him in order to play the game and search which the place can buy the
cheap Archlord gold. I am happy with him.
Do you know the fiesta Gold, in the game you need the
fiesta money. it can help you increase your level. My friends always asked me how to
buy fiesta Gold, and I do not know he spend how much money to buy the
fiesta online gold, when I see him in order to play the game and search which the place can buy the
fiesta online money. I am happy with him.

cheap rappelz rupees said...

I like play online game, I also buy ragnarok online zeny and ro zeny, the ragnarok zeny is very cheap, and use the iro zeny can buy many things, I like cheap zeny, thanks, it is very good.

I like play online game, I also buy rupees and rappelz rupees, the rappelz gold is very cheap, and use the rappelz money can buy many things, I like cheap rappelz rupees, thanks, it is very good.

buy aion kina said...

Now do you worried about that in the game do not had enough aion kina to play the game, now you can not worried, my friend told me a website, in here you can buy a lot aion online kina and only spend a little money, do not hesitate, it was really, in here we had much aion gold, we can sure that you will get the cheap aion kina, quick to come here to buy aion kina.