Notice:
1. The "entities" mentioned in this article are all generated by LINQ TO SQL (i.e. .dbml)
2. You need to understand how LINQ TO SQL implements table associations, EntitySet and EntityRef
Maybe after seeing the title, you will think that the question is relatively abstract, so let me give an example to explain the problem in detail.
In the N-tier architecture based on LINQ TO SQL, if we need to update an entity, the process should be as follows:
process
BLL.GetModel(p=>p.id==1) --> Modify the corresponding attribute (field) value --> BLL.Update(Entity entity) --> DAL.Update(Entity entity) --> Update successful
At this time, we need to pass this entity from the business layer (BLL) to the data access layer (DAL). When the GetModel method returns the entity, the DataContext will be released immediately, and then re-instanced when the DAL.Update (Entity entity) method is executed. Create a DataContext to perform the update operation; it can be seen that the passed entity is passed from the first DataContext to another DataContext. When operating across different DataContexts in LINQ TO SQL, additional operations need to be performed first, that is, context.Entity. Attach(entity,true); Finally context.SubmitChanges();
Let’s take a look at the code:
code
class BLL
{
private readonly DAL dal = new DAL();
public LinqToSqlProvider.User GetModel(Expression<Func<LinqToSqlProvider.User, bool>> expression)
{
dal.GetModel(expression);
}
public void Update(LinqToSqlProvider.User entity)
{
dal.Update(entity);
}
}
class DAL
{
public LinqToSqlProvider.User GetModel(Expression<Func<LinqToSqlProvider.User, bool>> expression)
{
LinqToSqlProvider.User entry = new CriTextBroadcast.LinqToSqlProvider.User();
using (CriTextBroadcastDBDataContext context = DataContext)
{
entry = context.User.SingleOrDefault(expression);
}
return entry;
}
public void Update(LinqToSqlProvider.User entity)
{
using (CriTextBroadcastDBDataContext context = DataContext)
{
context.User.Attach(entry, true);
context.SubmitChanges();
}
}
}
In fact, when we use the above code to operate, this exception will occur:
Attempted Attach or Add entity, this entity is not a new entity, may be loaded from other DataContext...
After checking a lot of information to no avail, I finally saw a similar question in a foreign post. It turns out that the table corresponding to this entity has several related tables, such as: User -> Message, User -> Images, etc.
Attributes such as EntitySet<Message> Message will be automatically generated when generating entity classes. Since we do not read out these associated contents together when obtaining the entity, an ObjectDisposedException will occur in these attributes when attaching (Attach). That is because the DataContext used to query this entity has been released.
Solution:
code
/// <summary>
/// The auxiliary LinqToSql entity is separated from the original DataContext
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="entity"></param>
public static void Detatch<TEntity>(TEntity entity)
{
Type t = entity.GetType();
System.Reflection.PropertyInfo[] properties = t.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
foreach (var property in properties)
{
string name = property.Name;
if (property.PropertyType.IsGenericType &&
property.PropertyType.GetGenericTypeDefinition() == typeof(EntitySet<>))
{
property.SetValue(entity, null, null);
}
}
System.Reflection.FieldInfo[] fields = t.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach (var field in fields)
{
string name = field.Name;
if (field.FieldType.IsGenericType &&
field.FieldType.GetGenericTypeDefinition() == typeof(EntityRef<>))
{
field.SetValue(entity, null);
}
}
System.Reflection.EventInfo eventPropertyChanged = t.GetEvent("PropertyChanged");
System.Reflection.EventInfo eventPropertyChanging = t.GetEvent("PropertyChanging");
if (eventPropertyChanged != null)
{
eventPropertyChanged.RemoveEventHandler(entity, null);
}
if (eventPropertyChanging != null)
{
eventPropertyChanging.RemoveEventHandler(entity, null);
}
}
After obtaining the entity, you should use the Detach(entity) method to separate the entity from the original DataContext, and then attach it to the new DataContext.