Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / WinMobile

LINQ to SQL - Detach Entities

5.00/5 (9 votes)
12 Jul 2009CPOL7 min read 103.7K   1K  
Easily detach entities when using LINQ to SQL

Introduction

How do I use disconnected entities with LINQ to SQL? Every developer that has used LINQ to SQL has asked this question in dismay. Where is the detach? How do I use these entities with services, JSON, encapsulation, and multiple data contexts? All are normal questions when building a solid framework. By default, LINQ to SQL does not like to let go of its entities and does not see a reason that an entity should be disconnected from its context. PLINQO makes it very simple to detach LINQ to SQL entities. We are going to walk you through how this was implemented in PLINQO by showing how to manually add the detach functionality using LINQ to SQL.

Getting Setup

We will use the Petshop database to demonstrate detaching LINQ to SQL entities. A RowVersion column of type timestamp has been added to each table in the Petshop database. LINQ to SQL uses the RowVersion column to perform an optimistic concurrency check and will not allow you to attach entities as modified to the datacontext without a RowVersion column or setting UpdateCheck to Never (default is Always) on each entity property in the DBML.

Detaching from a DataContext

To ensure that an entity is completely detached, we need to examine each child entity, child entity list and every property that is delay or lazy loaded. We are going to walk through the process of implementing detach on the Product entity. Here is a look at the Product entity and all of its dependencies in the DBML designer.

Image 1

The Product entity contains all the different scenarios we have come across when detaching entities. Category is a child entity, Item is a child entity list and we have configured Descn to delay or lazy load.

To detach the Product entity, the first thing we need to do is create a Product partial class. To create the partial class for the Product entity, right click the Product entity in the LINQ to SQL designer, select "View Code" and a partial class is created for the Product entity. We will add the following method to the Product partial class:

C#
partial class Product
{
    public override void Detach()
    {
        if (null == PropertyChanging)
            return;

        PropertyChanging = null;
        PropertyChanged = null;
    }
}

First, a check is made to verify that the entity is attached to a context. This might be considered a bit of a hack, but LINQ to SQL entities participate in the DataContext's change notification through their PropertyChanged and PropertyChanging event handlers. The LINQ to SQL's DataContext tracks objects using the INotifyPropertyChanging and INotifyPropertyChanged interfaces. This means that the PropertyChanged and PropertyChanging events manage the attachment to the DataContext. A check to verify whether this event is being handled or not lets me know if the entity is attached to a datacontext.

C#
if (null == PropertyChanging)
    return;

If the entity is not attached to a datacontext, no work needs to be done. Also, this check eliminates the possibility of circular references causing any stack overflow issues. If the entity is attached to a datacontext, then the event handlers for the PropertyChanging and PropertyChanged events are removed.

C#
PropertyChanging = null;
PropertyChanged = null;

Now that the event handlers have been removed, changes for the entity are no longer being tracked. However, we aren't done detaching the Product entity. We must detach all of its child entities, lists and the properties that are delay loaded. So, we will do that now.

To implement the detach for all the child entities, lists and the delay loaded properties, we must create an abstract base class called LinqEntityBase from which all the entities inherit.

C#
public abstract partial class LinqEntityBase
{
    public abstract void Detach();
}

Since we will implement some base methods to take advantage of reuse that will need to use the Detach() method from each entity, an abstract detach method will be needed in the LinqEntityBase class. We have already seen the Detach() for the Petshop Product entity. Each of the other entities in the Petshop.dbml will need a detach specific to that entity. So, Product will now inherit LinqEntityBase.

C#
partial class Product : LinqEntityBase

So, now let's look at what we need to do to detach a child entity. We will add the following method that specifically detaches a child entity to our LinqEntityBase class.

C#
protected static System.Data.Linq.EntityRef<TEntity> Detach<TEntity>
	(System.Data.Linq.EntityRef<TEntity> entity) 
    	where TEntity : LinqEntityBase
{
    if (!entity.HasLoadedOrAssignedValue || entity.Entity == null)
        return new System.Data.Linq.EntityRef<TEntity>();
        entity.Entity.Detach();
    return new System.Data.Linq.EntityRef<TEntity>(entity.Entity);
}

We must first determine if the entity has been loaded. The trick here is to not trigger any loading of entities. The HasLoadedOrAssignedValue method tells us whether the entity has been loaded or not and we can avoid any lazy loading of entities. Once we determine the entity has been loaded, the entity is detached and returned as the target of a new EntityRef instance. If the entity has not been loaded, the property is set to a new empty instance of EntityRef.

C#
entity.Entity.Detach();

This line calls the Detach implementation that is specific in this case to the Category entity. Again, each entity requires its own Detach method specific to that entity. We have implemented a Detach() method on each entity similar to the process we are using for the Product entity.

C#
partial class Category : LinqEntityBase
{
    public override void Detach()
    {
        if (null == PropertyChanging)
            return;
               
        PropertyChanging = null;
        PropertyChanged = null;
        this._Products = Detach(this._Products, attach_Products, detach_Products);
    }
}

All child lists for an entity must be detached as well. The ItemList property on the Product entity is an EntitySet. Each ItemList in the EntitySet must be detached and the following method is needed to accomplish this.

C#
protected static System.Data.Linq.EntitySet<TEntity> Detach<TEntity>
	(System.Data.Linq.EntitySet<TEntity> set, Action<TEntity> onAdd, 
	Action<TEntity> onRemove) 
    	where TEntity : LinqEntityBase
{
    if (set == null || !set.HasLoadedOrAssignedValues)
        return new System.Data.Linq.EntitySet<TEntity>(onAdd, onRemove);
            
    // copy list and detach all entities
    var list = set.ToList();
    list.ForEach(t => t.Detach());
        
    var newSet = new System.Data.Linq.EntitySet<TEntity>(onAdd, onRemove);
    newSet.Assign(list);
    return newSet;
}

As we mentioned before, HasLoadedOrAssignedValue is used to determine if the list has been loaded and avoids lazy loading the list. Each item in the ItemList must be detached and copied to a new EntitySet that is not attached to a datacontext.

Lastly, any delay loaded properties will need to be detached. By updating the DBML, I have configured the Descn property of the Product entity to be delay loaded.

Image 2

Any delay loaded properties also hold a connection to the datacontext and must be detached and uses the third and last Detach method we will need in the base class.

C#
protected static System.Data.Linq.Link<T> Detach<T>(System.Data.Linq.Link<T> value)
{
    if (!value.HasLoadedOrAssignedValue)
        return default(System.Data.Linq.Link<T>);
            
    return new System.Data.Linq.Link<T>(value.Value);
}

As has been the pattern, check to see if the object has been loaded, if not return a default instance, otherwise, return a new instance of Link with the value of the object as the target of the instance.

Now that we have added the necessary base methods for detaching the child entities, child entity sets and delay loaded properties, we can complete the Product Detach method by adding a Detach call for the Category, ItemList and Desc property. Below is the complete Product Detach() method:

C#
partial class Product : LinqEntityBase
{
    public override void Detach()
    {
        if (null == PropertyChanging)
            return;
            
        PropertyChanging = null;
        PropertyChanged = null;
            
        this._Category = Detach(this._Category);
        this._Items = Detach(this._Items, attach_Items, detach_Items);
        this._Descn = Detach(this._Descn);
    }
}

Using Detach

One way to take advantage of detach and reattaching Linq to SQL entities is to use the repository pattern. We have setup a simple OrderRepository to get and save Orders.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Detach.Repositories
{
    public class OrderRepository
    {
        public static Order Get(int orderId)
        {
            Order order = null;
            using (var context = new PetshopDataContext())
            {
                order = context.Orders.FirstOrDefault(o => o.OrderId == orderId);
                order.Detach();
            }
            return order;
        }
        
        public static Order Save(Order order)
        {
            using (var context = new PetshopDataContext())
            {
                if (order.OrderId > 0)
                    context.Orders.Attach(order, true);
                else
                    context.Orders.InsertOnSubmit(order);
                    
                context.SubmitChanges();
                order.Detach();
            }
            return order;
        }
    }
}

As you can see, each of these methods uses its own datacontext. There is no need to pass one datacontext around as a parameter or hold it in a module level variable. The entities are completely disconnected which means you are free to use the entities anywhere you like without worrying about the datacontext. The ability to detach makes the repository pattern possible with LINQ to SQL. The code below interacts with the repository and is not concerned with datacontexts or maintaining a connection to the database.

C#
Order order = new Order();
order.BillAddr1 = "0001 Cemetery Lane";
order.BillCity = "Westfield";
order.BillState = "NJ";
order.BillZip = "07090";
order.BillCountry = "US";
order.Courier = "DHL";
order.OrderDate = System.DateTime.Now;
order.TotalPrice = 0;
order.AuthorizationNumber = 1;
order.BillToFirstName = "Gomez";
order.BillToLastName = "Adams";
order.Locale = "blah";
order.ShipToFirstName = "Gomez";
order.ShipToLastName = "Adams";
order.ShipAddr1 = "0001 Cemetery Lane";
order.ShipCity = "Westfield";
order.ShipState = "NJ";
order.ShipZip = "07090";
order.ShipCountry = "NJ";
order.UserId = "gadams";
    
order = OrderRepository.Save(order);
    
order.UserId = "gadams2";
order = OrderRepository.Save(order);
    
order = OrderRepository.Get(2);
order.TotalPrice = 150;
order = OrderRepository.Save(order);

Conclusion

As you can see, it is a bit of work to detach an entity from a datacontext. Also, as the object graph gets more complicated, it can be tricky to ensure that the entity is completely detached. The PLINQO detach takes all precautions when detaching from the datacontext and ensures an entity can be used in a disconnected manner. The ability to use LINQ to SQL entities disconnected from the datacontext opens up many opportunities for encapsulation and reuse.

PLINQO generates detach methods similar to the one we just built for the Product entity for each entity. PLINQO figures out all the necessary child objects, lists and delay loaded properties that need to be detached and makes sure the proper detach methods for those entities are executed when detaching an entity from the datacontext. This means you do not have to worry about what is needed to detach your entities. PLINQO has already taken care of it.

So, how do you detach entities when using PLINQO? Call the Detach method. DONE!

History

  • 12th July, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)