20 acres ~ 360 degree mountain top views ~ Log home ~ $799,000

Just spent another couple days trying to track down and work around an issue I was having with LINQ 2 SQL and the current entity structure that is used.  The problem is basically, the change tracking routines to keep up with what changes you have made to an entity, is handled by the DataContext and not in your entity.  If you close your DataContext, you will have no change tracking.  Even worse yet, you will have problems "attaching" that instance to a new DataContext unless it is truly detached and you have a copy of the original data that the attach method can use to see what changes were made.

Boy or boy, there was a lot of fun tracking all this stuff down.  Here is a post that talks about these kind of issues:

http://west-wind.com/weblog/posts/135659.aspx

In my books, the problem is that LINQ 2 SQL does not go far enough to provide features to make it fully useful when workign with disconnected data.  I understand the desire to have lightweight entities and simplistic syntax, but a couple of extra features could really help out.  Currently, I am working with VS 2008 Beta 2 and changes to the Attach are expected in the RTM, but I still do not think they will go as far as I need.

My goal is simply an entity that keeps track of its own original state to use when I wish to attach an entity to complete changes.  So, the first step I wanted to handle is adding a new property and instance variable for the original state.

In the follow example, let us say we have a database table such as:

table Customer:
Customer_ID int NOT NULL
Phone nvarchar[26] NOT NULL
CustomerName nvarchar[128]

Okay, I use the designer and create a LINQ 2 SQL class file by dropping the table on it.  Now add another class file to the project and a partial class for my Customer class.  In this class I will drop an variable called "original".  I also add a property called "Original":

public partial class Customer
{
     internal Customer original = null;

    public Customer Original
    {
       get { return original; }
       set { original = value; }
    }
}

Now, I wanted a method to copy all the fields of the class, but did not want to bother with reflection or anything like that.  I just made a CopyData method which copied the data properties from the old to the new instance without copying the "original" variable.  Then I build a Clone method that fully copied the instance along with the Original value (I used static methods but you can make the instance methods:

internal static void CopyData( Customer newCustomer, Customer oldCustomer)
{
    if(oldCustomer == null)
   {
       newCustomer = null;
   }
   else
   {
      newCustomer.Customer_ID = oldCustomer.Customer_ID;
      newCustomer.Phone = oldCustomer.Phone;
      newCustomer.CustomerName = oldCustomer.CustomerName;
   }
}

public static Customer Clone(Customer oldCustomer)
{
   Customer newCustomer = new Customer();
   CopyData(newCustomer, oldCustomer);

   Customer newOriginal = new Customer();
   Customer oldOriginal = oldCustomer.Original;
   CopyData(newOriginal, oldOriginal)

   newCustomer.Original = newOriginal;
   return newCustomer;
}

Now, the only thing left to do is to copy off the original value when read in.  There is a method called OnLoaded() that will fit that bill!

partial void OnLoaded()
{
   original = Clone(this);
}

That is a done deal now.  Our Entity will carry a copy of its original data. To make a copy that we want to use disconnected from the DataContext, we would use the Clone() method to make the instance such as:

Customer myNewSpiffyEntity = Customer.Clone(myFoundEntityIWantToUse);

It is now safe to do what we want, we can pass this up through tiers or just about anything we want and when the time to update has arrived we simply call:

DataClassesDataContext db = new DataClassesDataContext();

db.Attach(myNewSpiffyEntity, myNewSpiffyEntity.Original);
db.SubmitChanges();


That is all there is to it.  The entity is still simple, yet it tracks its changes to the individual properites.  Of course, this will not work in all situations, but in basic needs, this can go a LONG ways to making LINQ much more useful for disconnected data manipulation.  I know I will use this a lot in my web applications without the need to persist DataContext'es or any funky stuff like that.

Now if only this featuer was built in or at least offered as an option inside the designer so I did not have to go through this extra effort on each class I plan to offer this functionality.  I did ponder how nice it would be to simply click an option in the designer or a property on the table inside the entity editor, to tell it to generate the copydata, clone and OnLoaded to maintain the original state.  I see a number of posts out there where it appears people would like to have the entities manage state when not handled by the DataContext.

posted on Saturday, November 03, 2007 4:50 AM
Filed Under: Misc   ** All Categories   Databases  

Comments

Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
Posted by Justin
on 2/11/2008 10:46 PM
The major problem I see with this is your clone method. Not that your code does not work, it is just a lot of work to add that method for every table. I mean it is probably the same amount of needed to do it in ado. Of course we could use reflection, but we don't want to do that now do we.
Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
Posted by Thinker
on 2/12/2008 12:27 AM
Yes, it is a hassle to say the least. Would be nice if that was built in so we did not have to do it. I have thought of maybe a VS add-on to handle this step. Sure would be nice :)
Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
Posted by Chris
on 2/23/2008 5:49 PM
with a little reflection and generic classes we have this:


public class detachableEntity<T> where T : detachableEntity<T>, new()
{
public void OnDataLoaded()
{
original = Clone();
}
public T original { get; set; }

public T Copy()
{
return Copy((T)this);
}
public static T Copy( T Old)
{
T newItem = Clone(Old);
newItem.OnDataLoaded(); // set the original state for the new object to the currect state
return newItem;
}
public T Clone()
{
return Clone((T)this);
}
public static T Clone(T item)
{
if (item == null)
return null;

T newItem = new T();
// copy all subclass properties.
foreach (PropertyInfo prop in item.GetType().GetProperties())
{
PropertyInfo prop2 = item.GetType().GetProperty(prop.Name);
prop2.SetValue(newItem, prop.GetValue(item, null), null);
}
//the two items now share the same orginal state object, fix this by
//cloning the orginal state object on the item that to create a new original state
//object for the new item
if( item.original != null)
newItem.original = item.original.Clone();
return newItem;
}
}

add this to the partial class to enable it.

partial class Address : detachableEntity<Address>
{
partial void OnLoaded(){base.OnDataLoaded();}

}
The exact same pattern is used for every class so you can use replace all to add it to all partial classes.

here is a usage example

Address home = new Address();
AdventureWorksDataContext AW = new AdventureWorksDataContext();
home = AW.Addresses.First();
AW = null;
home.City = home.City + "AAA";
AW = new AdventureWorksDataContext();
AW.Addresses.Attach(home, home.original);
ChangeSet Changes = AW.GetChangeSet();

-Chris
Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
Posted by Thinker
on 2/23/2008 9:33 PM
Very nicely done Chris! That should really come in handy!
Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
Posted by Chris
on 2/23/2008 11:03 PM
Thanks.
I have a post with better formatting on my site
Detached Data in DLinq
Chris
Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
Posted by Thinker
on 2/24/2008 9:45 AM
Thanks Chris! For those that did not notice the link to Chris's new blog and the post about detached Linq, it is:

http://condron.wordpress.com/2008/02/24/detached-data-in-dlinq/

Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
Posted by Alexander
on 3/6/2008 11:02 AM
Hello.
I use code from http://condron.wordpress.com/2008/02/24/detached-data-in-dlinq/

Thank you for useful class but there is one problem.
When I use the entity which is derived from detachableEntity class the code below doesn’t work:

using (EntitiesDataContext dx = new EntitiesDataContext(connectionString))
{
suser user = dx.susers.FirstOrDefault(s => s.Login == “Alex”); //only for test. This user is present in database

user.email = “test@xxxx.com”;
dx.SubmitChanges();
}

There is exception:
An exception of type ‘System.Data.SqlClient.SqlException’ occurred in System.Data.Linq.dll but was not handled in user code

Additional information: Cannot insert duplicate key row in object ‘dbo.suser’ with unique index ‘IX_suser’.

I think LINQ generate insert coomand isntead of update. If I don’t use detacheble class as derived all is ok.
Can you help with that problem.

Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
Posted by Thinker
on 3/6/2008 11:45 AM
I have not had the time to try out Chris's code yet. You might post this to his blog where you got the code. I have only used the method I described in the article above by manually cloning the objects when needed.
Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
on 3/14/2008 4:39 AM
Hi!,

There's been a lot of people dissapoint with the lack of a disconnected change tracking for Linq to SQL!
Few months before, a Microsoft guy working in Linq to SQL (Matt Warren) talked about a solution for this, but it didn't get to the current release.
It was about a mini-connectionless Datacontext that serializes ann travels to the client, when returns deserializes with his mini changeset.

A different approach is including state flags in every entity, we took that approach so I wrote a blog entry explaining our implementation

http://pampanotes.tercerplaneta.com/2008/03/implementing-n-tier-change-tracking.html
Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
Posted by Thinker
on 3/14/2008 9:42 AM
Thanks for the post, interesting read!

The part that I like about a disconnected object with only original state instead of a full state tracking is that you can use it without having to pass another container/context manager of some type. With passing around a state manager, it ends back more like a dataset instead of individaul objects. And we still have datasets we can use for that if so desired.

It nice to pass a single object and not have the added overhead, all you have is the object and a copy of that object. The deletion or addtion of objects are still kept as connected calls, but the manipulation of the data can be disconnected so it still stays a bit lighter than datasets.


Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
Posted by Dan
on 2/9/2009 10:00 AM
There is also another approach. Let's say we had a person object with two properties FirstName and LastName. So the caller when wanting to save would call a method like the following:

public void SavePerson( Person p )
{
DataClasses1DataContext dc = new DataClasses1DataContext();

Person person = new Person();

person = dc.Persons.Single( x => x.ID == p.ID );

person.FirstName = p.FirstName;
person.LastName = p.LastName;

if ( p.ID == 0 )
{
dc.Persons.InsertOnSubmit( person );
}

dc.SubmitChanges();
}

///////////////////////////////
Now you could instead of copying each individual property use reflection as someone posted earlier
Gravatar
# re: LINQ - Disconnected-Attach, no change tracking fix..
Posted by Thinker
on 2/9/2009 10:59 AM
Yes Dan, you can do that and many people use this method. The problem is that you are reading the data twice from the server, the first time when you get the data and then again when you want to save the changes.

What would be really great is if they give us some form of generic build in collections that track changes allow the collection of items to be maintained even through serialization. Of course, Datatables and Datasets handle these kinds of things to a certain degree, but it would be nice to have a form of for example that had change tracking and would maintain original values. This would not only be handy for SQL 2 SQL, but would be good for all data form type editting enabling the reversal of changes within the collection (even for deleted items).

For my internal data that requires editing I do not mind having a second copy, the original and the active copies so that I always have access to the original. While I would not use that method in every situation, it would be nice to have the collection to handle these issues automatically when I so desired.
Post a comment










 

Please add 2 and 1 and type the answer here: