Those things of LINQ (9)-exceptions and solutions caused by parsing Table. Attach

Source: Internet
Author: User

The main reason is that some friends in the blog Park started to discuss the problem of LINQ2SQL. Attach is used this time. By interpreting Attach, we can find out how LINQ2SQL maintains and tracks object instances and how to implement delayed loading, it can also lead to discussions on the Application skills of LINQ2SQL in delayed loading and N-Tier Application. This article applies to LINQ2SQL of. Net Framework 3.5. The database used is Northwnd.

For object addition and deletion operations, LINQ2SQL provides InsertOnSubmit ()/DeleteOnSubmit () in the Table <T> class definition (). For object updates, because LINQ2SQL adopts the Object Tracking Mechanism (refer to LINQ2SQL object lifecycle management), we do not need to explicitly notify DataContext after modifying object attributes, when you call DataContext. submitChanges () automatically submits the modifications to the database for storage. This context-based operation is very convenient, otherwise there will be a lot of Update calls in the code, but there are also restrictions-only within the scope of the same DataContext object, the changes made to the object will be saved at SubmitChanges. For example:

            using (var context = new Northwnd())            {                var customer = context.Customers.First();                customer.City = "Beijing";                context.SubmitChanges();            }

During Web and N-Tier Application development, data queries and updates are often not satisfied in the same DataContext. Therefore, the LINQ2SQL class defines the Attach method in the Table <T> class, this interface is used to associate an object that has been disconnected from the query DataContext context to the DataContext object to which the Table belongs. In this way, you can use the new DataContext to update the object. For example:

            Customer customer = null;            using (var context1 = new Northwnd())            {                customer = context1.Customers.First();            }            customer.City = "Beijing";            using (var context = new Northwnd())            {                context.Customers.Attach(customer);                context.SubmitChanges();            }

But the problem is that the code execution error throws the following exception:

System. NotSupportedException: An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported.

 

This problem is no longer a new problem. There are many solutions for google, but why does this seemingly normal code throw an exception? In fact, it is still related to the scope of DataContext. This article will try to analyze this issue and discuss some tips for using LINQ2SQL in the N-Tier Application.

Are they all caused by Association?

The error occurs in System. Data. Linq. Table <T>. Attach (TEntity entity, bool asModified). If one of the following conditions is met, the Attach call will fail:

1. DataContext is read-only, which is equivalent to setting DataContext. ObjectTrackingEnabled = false. Read-Only DataContext can only query data, and other operations will throw an exception.

2. When Attach is called, DataContext is performing the SubmitChanges () operation.

3. The DataContext object has been explicitly called Dispose () for destruction.

4. asModified is true, but the TEntity class property ype ing information (UpdateCheck = WhenChanged or UpdateCheck = Always) contains the settings. This is because the Attach object does not have a record of the original value in the current DataContext, and DataContext cannot generate the where clause based on UpdateCheck settings to avoid concurrency conflicts. It should be noted that asModified = true is hardly used, especially in Web application scenarios such as displaying queries, modifying users, and submitting and saving data, this article will discuss how to operate such a scenario later. If you insist on calling with asModified = true, you can add the RowVersion attribute definition in the TEntity class. LINQ2SQL introduces RowVersion to provide another method for conflict detection except UpateCheck, since RowVersion should be modified after each update operation, it generally corresponds to the Timestamp type.

5. Attach an object that already belongs to the current DataContext context.

6. The Attach object contains the Assocation attribute not loaded or the nested Association attribute not loaded.

The reason (6) is discussed in this article. Let's take a look at the call in the Attach function.

...if (trackedObject == null)    {        trackedObject = this.context.Services.ChangeTracker.Track(entity, true);    }...

Call StandardChangeTracker in the Attach function. track (TEntity entity, bool recursive) method. Note that the second parameter indicates recursion. Calling Track (entity, true) by Attach will check all nested Association attributes of entity. The Code throws an exception in StandardChangeTracker. Track:

...if (trackedObject.HasDeferredLoaders){throw Error.CannotAttachAddNonNewEntities();}....

 

Let's take a look at what trackedObject. HasDeferredLoaders has done:

internal override bool HasDeferredLoaders{    get    {        foreach (MetaAssociation association in this.Type.Associations)        {            if (this.HasDeferredLoader(association.ThisMember))            {                return true;            }        }        foreach (MetaDataMember member in from p in this.Type.PersistentDataMembers            where p.IsDeferred && !p.IsAssociation            select p)        {            if (this.HasDeferredLoader(member))            {                return true;            }        }        return false;    }}

We will soon find the key point. Let's take a look at this. HasDeferredLoader:

private bool HasDeferredLoader(MetaDataMember deferredMember){    if (!deferredMember.IsDeferred)    {        return false;    }    MetaAccessor storageAccessor = deferredMember.StorageAccessor;    if (storageAccessor.HasAssignedValue(this.current) || storageAccessor.HasLoadedValue(this.current))    {        return false;    }    IEnumerable boxedValue = (IEnumerable) deferredMember.DeferredSourceAccessor.GetBoxedValue(this.current);    return (boxedValue != null);} 

Answer: storageAccessor. hasAssignedValue checks whether the Association attribute is assigned a value (for EntityRef) and storageAccessor. hasLoadedValue checks whether the Association attribute has been loaded (for EntitySet). If no value is assigned or loaded, the DeferredSource object obtained by GetBoxedValue is not empty, an exception is thrown.

 

Why does Attach throw an exception in this case? First, you must understand the delay source object, which is the key to implementing delayed loading in LINQ2SQL. In the delayed loading mode (DataContext. DeferredLoading = true), The EntitySet and EntityRef attributes generate database queries only when they are accessed. Taking EntitySet as an example, when GetEnumerator () is called:

public IEnumerator<TEntity> GetEnumerator(){    this.Load();    return new Enumerator<TEntity>((EntitySet<TEntity>) this);}

This. Load calls the delayed loading of source data:

public void Load(){    if (this.HasSource)    {        ItemList<TEntity> entities = this.entities;        this.entities = new ItemList<TEntity>();        foreach (TEntity local in this.source)        {            this.entities.Add(local);        }...    }}

Further tracing will be made to System. Data. Linq. CommonDataServices. GetDeferredSourceFactory (MetaDataMember) and System. Data. Linq. Mapping. EntitySetValueAccessor. When the DataContext object initializes the model information, it will call GetDeferredSourceFactory to generate the corresponding DeferredSourceFactory object for the specified property. The factory object generates a delay source object through CreateDeferredSource. During the query operation, DataContext will call the SetSource method of the EntitySet attribute of each object to bind a delay source for each EntitySet. The delay source will call DataContext to implement delayed loading, in this way, the EntitySet and DataContext are decoupled to make the POCO class smarter. For EntitySet, when delayed loading is executed, the delay source is cleared and the corresponding loaded flag is set to true.

Next, let's verify. For the convenience of examples, I only keep the Orders of the Customer class as the unique Association attribute: (the article will finally provide code download. If you are interested, follow the verification instructions)

            Customer customer = null;                        using (var context = CreateNorthwnd())            {                customer = context.Customers.First();                // forces to load order association                customer.Orders.Count.Dump();            }            customer.City = "Beijing";            using (var context = CreateNorthwnd())            {                context.Customers.Attach(customer);                context.SubmitChanges();            }

Don't worry, it's still wrong! Although the call of customer. Orders. Count has loaded customer. Orders, the Order object also contains several unloaded Association attributes. You can just remove the Association attribute definition of the Order object!

After analysis, you understand why exceptions are thrown when Association or nested Association is not assigned or loaded and the delay source is not empty? This is because, like an object that requires Attach, The DataContext object associated with the delayed source has been destroyed, and the delayed source cannot load data. Therefore, DataContext rejects the associated objects.

So much has been said to help you understand why exceptions occur. The solution is simple, and you do not need to modify the definition of an object. It is also one of my best practices for LINQ2SQL:

            Customer customer = null;            using (var context = CreateNorthwnd())            {                var option = new DataLoadOptions();                // disabled the deferred loading                context.DeferredLoadingEnabled = false;                // specify the association in needed                option.LoadWith<Customer>(c => c.Orders);                context.LoadOptions = option;                customer = context.Customers.First();            }            customer.City = "Beijing";            using (var context = CreateNorthwnd())            {                context.Customers.Attach(customer);                context.Refresh(RefreshMode.KeepCurrentValues, customer);                context.SubmitChanges();            }

First, we disable the delayed loading of DataContext and explicitly specify the associated data to be loaded through DataLoadOption. This solution not only solves the Attach problem, it also avoids exceptions caused by delayed loading in the N-Tier Application.

LINQ2SQL Best Practices

At the end of this article, I will summarize some of my experiences in applying LINQ2SQL.

1. We recommend that you use using to use the DataContext object, so that the DataContext object is destroyed immediately after the operation is completed. By default (ObjectTrackingEnabled = true), DataContext saves a copy of the query object in the memory. If you keep the DataContext object for a long time, it will cause unnecessary memory usage.

2. For query-only operations, set ObjectTrackingEnabled to false. Disabling Object Tracking helps improve the query performance of DataContext. This is also called read-only DataContext. Then, set DataLoadOption. AssociateWith () and DataLoadOption. LoadWith () based on the required data to achieve efficient query.

3. When using LINQ2SQL to write N-Tier applications, it is recommended to disable delayed loading, because the trouble is far greater than the benefit. Note: When ObjectTrackingEnabled = false, delayed loading is unavailable, which is equivalent to DataContext. DeferredLoading = false.

4. Attach (entity, false) + DataContext. refresh (RefreshMode. keepCurrentValues, entity) + SubmitChanges () to update the disconnected object, which avoids the DuplicateKey issue, as shown in the code above.

Download

Demo Code: http://files.cnblogs.com/chwkai/LinqAttach.rar

Link

Those things of LINQ (total)

What about LINQ (6)-object lifecycle management

Related discussions

Qilin's post has aroused a lot of discussions and is quite interesting:

1. "The method signature does not contain the entities of linq to SQL, so the method code block must definitely appear. I think the open-source projects of other people are converting the DomainModel into the Entity of linq to SQL when accessing the database, so that they use the linq to SQL ."

For N-Tier applications, the Application scope of DomainModel (domain object) is between the Presentation Layer and Business Layer, while the POCO class generated by LINQ belongs to DataModel, the application scope is N-Tier. In terms of concept, DomainModel and DataModel are different, but in most 3-tier applications, DomainModel and DataModel are the same class-entity class. As for why the "open-source projects" will do this, I think most of them were originally not developed using LINQ, but were transplanted later?

2. "First query the original product based on the id of the Input product object, and then use reflection to automatically copy the new property"

DataContext provides a Refresh () function to read the Database Value of entity, and then Refresh the current value or original value of entity through the specified RefreshMode. Before updating entity, we need to Attach the object first, because. refresh (RefreshMode. keepCurrentValues, entity) can obtain the original value of the entity and hand over the work of determining whether the entity has been changed to DataContext. Therefore, we only need to call Attach (entity) or Attach (entity, false ), instead of calling Attach (entity, true) or Attach (entity, originalEntity), you do not need to copy it.

3. "It is too powerful to instantiate the NorthwindDataContext. You Need To Know That datacontext is a large object and you should avoid repeated instantiation. It is best to have only one instance at a request, and your problem will be solved ."

As mentioned in the Design Intent of LINQ2SQL, the application mode of LINQ2SQL is "Unit of work", that is, "Create-call-Destroy, the purpose is to quickly release DataContext after the call. Due to the resources occupied by saving object copies and SQL connections, DataContext provides sufficient mechanisms to ensure that the consumption of Instantiation is acceptable. However, if you keep the DataContext object in the keep of an http request, it will cause unnecessary memory usage.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.