Wednesday, April 7, 2010

Now what do I do with these detached objects!?

Introduction
So I know all of you that actually read my initial post are thinking, "Great... Now I have detached objects.  I spent an hour making the modifications you told me to, but I still can't do anything with them!"  So take a deep breath and lets cover how to save your detached Linq 2 SQL entities.


First, Lets take advantage of those partial L2S Entities
Some of you know what partial classes are, some of you may not.  So lets begin there.


"A partial class, or partial type, is a feature of some object oriented computer programming languages in which the declaration of a class may be split across multiple source-code files, or multiple places within a single file."  You can read more here: http://en.wikipedia.org/wiki/Partial_Classes


.NET requires the class be the same name and be in the same namespace.  So lets go back to our User example.


User
int UserID
EntityRef<UserType> _UserType
EntityRef<Country> _Country
EntitySet<Address> Addresses


Your definition for this class likely looks similar to:  

[Table(Name="dbo.USER")]
[DataContract()]
public partial class USER : INotifyPropertyChanging, INotifyPropertyChanged {.......

So my suggestion create a *.CS file named something along the lines of "EntityExtensions.cs"

I know we have a class already taking advantage of this if you are following from yesterday's post, however, lets re-iterate what we have again.








public partial class User
{
  public void DetachAll()
  {
     this._Country = default(EntityRef);
     this._UserType = default(EntityRef);
     if(this._Addresses.HasLoadedOrAssignedValues)
        foreach(Address a in this._Addresses)
           a.DetatchAll();
  }

  private bool IsMarkedForDeletion;
  public void MarkForDeletion()
  {
    IsMarkedForDeletion = true;
  }
     public bool IsMarkedForDeletion()
     {
          return IsMarkedForDeletion ;
     }
  public bool IsNew()
  {
    return this._UserID &lt;= 0;
  }
}

Ok, now lets explain the two latest additions.  IsMarkedForDeletion is a handy way of deleting attached objects on one submit.  And IsNew is a handy way of inserting newly attached objects on one submit as well.  The same fields should be added to the Address class as well.

Next we need to know how to save these detached objects as efficiently as possible.  The following code would be in your data access layer.



public USER DeepSave(USER obj)
{
     using(YourDataContextObject data = new YourDataContextObject)
     {
          obj.DetachAll();

          if(obj.IsNew())
          {
               //Check the results, all attached objects are submitted on this single submit.
               data.USERs.InsertOnSubmit(obj);
               data.SubmitChanges();
          }
          else
          {
               data.USERs.Attach(obj, true);
               
               if(obj.ADDRESSes.HasLoadedOrAssignedValues)
               {
                    data.ADDRESSes.AttachAll(obj.ADDRESSes.Where(x =>; !x.IsNew() &amp;&amp; !x.IsMarkedForDeletion());
                    data.ADDRESSes.InsertAllOnSubmit(obj.ADDRESSes.Where(x =>; x.IsNew());
                    data.ADDRESSes.DeleteAllOnSubmit(obj.ADDRESSes.Where(x => x.IsMarkedForDeletion());
               }

               try
               {
                    data.SubmitChanges();
               }
               catch(ChangeConflictException ex)
               {
                    //Do something to handle Change Conflicts due to Optimistic Concurrency
               }
          }                    
     }

     //Now here comes the hack to detach a saved object, you can do this by shoving the object into a
     //serialized state.  Or do what I do below, this is a double database hit HOWEVER it is less painful than 
     //some of the other options.
     using(YourDataContextObject data = new YourDataContextObject())
     {
          data.ObjectTrackingEnabled = false;

          if(obj.ADDRESSes.HasLoadedOrAssignedValues)
          {
               DataLoadOptions dlo = new DataLoadOptions();
               dlo.LoadWith(x => x.ADDRESSes);
               data.LoadOptions = dlo;
          }

          obj = data.USERs.Single(x =>; x.UserID == obj.UserID);
     }    
}


In Summary
Now you can save and re-save your object and associated children as many times as you want.  The magic is in the detachment of the object from its data context.  This is why-what-how optimistic concurrency works.  The only thing to watch out for is when a RowVersion field does not match the RowVersion of an existing object, SubmitChanges will throw a ChangeConflictException.  This can be handled any number of different ways, most people choose to throw the error back to the UI and tell the user they need to refresh the error.  If you want to get more creative, you can enumerate the ChangeConflictException and tell exactly which fields the conflict occurred on and do a smart merge yourself.

I hope you have enjoyed part 2 (the final part) of how to handle n-tier and Linq.  Not sure what I'll post next, but it will be worth while to check back!

If you are looking for IT solutions for your businesses, would like to provide feedback, or give us a referral for new business opportunities (We provide profit sharing on referrals), you can visit our website at www.NightOwlCoders.com.  We service all of the US for Web Site design, and offer consulting services throughout Pittsburgh, Butler, Cranberry, and New Castle, PA.

~Aqua~


No comments:

Post a Comment