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.
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:
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
.
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.
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.
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
.
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.
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
.
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.
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.
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);
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.
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.
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:
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.
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 datacontext
s or maintaining a connection to the database.
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