Detached Data in DLinq

Update: before trying the code pattern here,  see if you can use this  instead.

– Chris

I was reading two good blogs about Linq to SQL and ASP.Net applications  here and here.

Based on the pattern set up by Rocky Moore (see first link) I came up with this code. Using generics and a little bit of reflection in a base class.

Here is the base class

   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 original 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;

      }

   }

In the partial classes created by Linq Designer here is how to add the base class

As the pattern is fixed, it is a good candidate for replace all in the partial class file.

   partial class Address : detachableEntity<Address>

   {

      partial void OnLoaded() { base.OnDataLoaded(); }

 

   }

and a usage example

static void Main(string[] args)

{

//Create new Entity

   Address home = new Address();

   //Create new data context

   AdventureWorksDataContext AW = new AdventureWorksDataContext();

   //Get Data

   home = AW.Addresses.First();

 

   // Disconnect Data Context

   AW = null;

 

   //Modify Data

   home.City = home.City + “AAA”;

 

   // New Data context

   AW = new AdventureWorksDataContext();

   //Attach Entity

   AW.Addresses.Attach(home, home.original);

   //Review Changes

   ChangeSet Changes = AW.GetChangeSet();

 

   //Update Data Source

   AW.SubmitChanges();

 

   //Dispose Data Context

   AW.Dispose();

   AW = null;

   //Dispose Entity

   home = null;

 

   //Create new Data Context

   using (AW = new AdventureWorksDataContext())

   {

      //Create new Entity

      Address NewHome = AW.Addresses.FirstOrDefault(addr => addr.City.Contains(“AAA”));

      //Modify Data (remove earlier changes)

      NewHome.City = NewHome.City.TrimEnd(new char[] { ‘A’ });

      //Submit Changes in the same context

      AW.SubmitChanges();

   }

}

This should be quite useful for detached applications.

-Chris

11 thoughts on “Detached Data in DLinq

  1. Hello.
    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.

  2. Hi Alexander,
    I tried to replicate your error, and I could not.
    Here is the code I used to try to replicate it.


    static void Main(string[] args)
    {
      //Create new Entity
      Address home = new Address();
       //Create new data context
       AdventureWorksDataContext AW = new AdventureWorksDataContext();
       //Get Data
       home = AW.Addresses.First();
      
       // Disconnect Data Context
       AW = null;
      
       //Modify Data
       home.City = home.City + "AAA";
      
       // New Data context
       AW = new AdventureWorksDataContext();
       //Attach Entity
       AW.Addresses.Attach(home, home.original);
       //Review Changes
       ChangeSet Changes = AW.GetChangeSet();
      
       //Update Data Source
       AW.SubmitChanges();
      
       //Dispose Data Context
       AW.Dispose();
       AW = null;
       //Dispose Entity
       home = null;
      
       //Create new Data Context
       using (AW = new AdventureWorksDataContext())
       {
         //Create new Entity
         Address NewHome = AW.Addresses.FirstOrDefault(addr => addr.City.Contains("AAA"));
         //Modify Data (remove earlier changes)
         NewHome.City = NewHome.City.TrimEnd(new char[] { 'A' });
         //Submit Changes
         AW.SubmitChanges();
       }
    }


    I am using vs2008 and sql server 2005
    I updated the usage example with this.

  3. Hi,

    I’ve used your DetachableEntity class as laid out here, and all works brilliantly during the update, however, there seems to be an issue when I try to delete an entity. I’m not sure if I’m doing something wrong, so I wanted to check if you had experienced any problems during deletes.

    All I have done is inherit one of my entity classes from DetachableEntity in a partial class, and provided an implementation of the OnLoaded partial method as described in your article.

    If I then call DeleteAllOnSubmit passing in an Expression<Func> predicate to match some criteria I don’t get any delete SQL hit my database. If I inspect the return value of the DataContext.GetChangeSet() I can see the same number of items in the Insert collection that are in the Delete section, and these items appear to be same. I guess the context is normalising this and determining that nothing needs to be deleted.

    If I remove the call to base.OnDataLoaded() in the partial class (effectively removing the assignment of the cloned entity), I can run through the same code but this time the GetChangeSet result doesn’t have anything the Inserts collection, and has the correct number in the Deletes section, so the rows are correctly deleted. But of course, the update method no longer works.

    My delete method is on a generic base class (and I realise that it will select the ones to delete before actually deleting them), but looks like this:

    public virtual void Delete(Expression<Func> predicate)
    {
    if (predicate == null)
    {
    throw new ArgumentNullException(“predicate”);
    }

    /// TODO: See if there’s a way we can delete items without having to
    /// perform a SELECT first.
    ///
    var query = DataContext.GetTable().Where(predicate);
    DataContext.GetTable().DeleteAllOnSubmit(query);
    DataContext.SubmitChanges();
    }

    I then call the method with something like Delete(t => t.SomeProperty == “foo”);

    Has anyone used this code and experienced anything similar?

    Thanks
    Ian

  4. Hi Ian,
    I have tried a few things and cannot reproduce your problem. Thinking about it I think I know what might be happening and how to check.
    In the onLoad function we do three things
    1) create a new instance of the type
    2) iterate through every property and copy it to the new type
    3) call this recursivly until this.original == null

    So either,
    Creating a copy of the item is causing something in your class structure to add the new item to insert list
    or
    Copying every property we find is adding a new item to the instert list

    I don’t think it is the recursion because your are only getting one extra per call.

    How to check:

    1) make DataContext public static (just for the test)
    2) set break points inside the clone function.
    3) evaluate [your class].DataContext.GetChangeSet() after each step

    This will tell you what step is creating the insert record.
    You can then find out the side efect code that is being invoked to add the insert.

    Let me know what you find.

    Thanks
    Chris

  5. Hi, thx for code,

    There is one problem. The reflection code doesnt work on child relations.
    I have a customer and i have orders of that customer.
    I load customer with deferredloadingEnabled = false AND dataloadOptions to load the orders of that customers
    Dim options As DataLoadOptions = New DataLoadOptions
    options.LoadWith(Of customer)(Function(x As customer) x.orders)

    When i use linq2sql without detachableEntity, i load 1 customer and 3 orders of that customer.

    With detachableEntity, cust1.orders.count = 0 (i expect 3) and the cust1.original.count = 3
    The problem is on this line when the property is an entityset.
    prop2.SetValue(newItem, prop.GetValue(item, null), null);

    So in that situation my original orders are at cust1.original.orders.original and my current orders are at cust.original.orders.

    A little strange, i think

    How can i fix this issue?

  6. Warning: This code won’t work when you have read only properties in your class. It will try to write the property and fail to do so because it has not setter for the property.

    Add the following line to you code in the detachableEntitypublic static T Clone(T item) method to avoid this:

    // copy all subclass properties.
    foreach (PropertyInfo prop in item.GetType().GetProperties())
    {
    PropertyInfo prop2 = item.GetType().GetProperty(prop.Name);

    // Check if the property can be written
    if(prop2.CanWrite)
    prop2.SetValue(newItem, prop.GetValue(item, null), null);
    }
    </code

  7. Chris,
    I implemented your detachableEntity class and I am trying to use an ObjectDataSource in an ASP.NET webpage to bind and update the entity class. For some reason, the Original property that the inheritance is adding to the entity is not being serialized (or at least it is null when the postback fires the update method). I have verified that it is set when the form loads but for some reason it is being cleared out. So when I call attach method as follows…

    context.Customers.Attach(instance, instance.Original);

    …it throws an exception stating that “Value cannot be null. Parameter name: original.”

    Any clue? Have you used your code in a similar fashion (i.e. GridView, DetailsView, etc.)?

  8. I tried this code and it works fine most of the time.
    only when I have two entities which share the same PK and have a one to one relationship.
    The PK will be lose after the clone.
    Does anyone encounter this problem? any workaround?

Leave a reply to Stijn Cancel reply