Attaching detached POCO to EF dbcontext-simple and fast

Source: Internet
Author: User

Introduction

Recently I was playing around with the Entity Framework (EF) and evaluating it for some projects. I had a very hard time figuring out what to attach detached the object graph to in DBContext fast and reliable the. Here I am sharing-simple method implementation The can do the for you AttachByIdValue() . If you aren't interested in full explanation of the problem jump straight to method implementation and start attaching yo ur objects.

The problem

Let's say we are using the EF in Web app to implement page for managing order and order Lines. So we had parent-child relation (order and order Lines) and some referential data that was displayed but won ' t be updated (Customer and products).

We would typically query above object graph from database (DB) using EF and send it to client (browser). When the client sends this object graph back to the server we would like to persist it and the order to does so we must first attach it to DbContext.

The question is what to attach this detached graph without reloading it form DB and applying changes. Reloading Form DB is performance hits and it is invasive. If I couldn ' t do it without reloading I would discard EF because it's very basic task that I expect my ORM to solve EAS ily. Luckily I found the solution after lot of digging.

ADD () or Attach ()

There is methods for attaching detached objects, Add() Attach() and, and they receive graph root object (Order). Add () method attaches all objects in graph and marks them as Added, while Attach () als o attaches all objects on graph but marks them as unchanged.

Since Our object group would usually have new, modified and unchanged data we only option was to use one of these DS to attach the full graph and then traverse the graph and correct state of each entry.

So which method should we choose?

Well actually Attach are not a option because Attach can cause key conflicts due to duplicate key values for same object T Ypes. If we have an order with a new order Lines, those order Lines would probably has an Id = 0. Attaching this order with Attach method would break because Attach would mark these the order Lines as unchanged and EF ins Ists that all existing entities should has unique primary keys. This is why we'll be using the Add method for attaching.

Resolving new and modified data by Id value

The question is how would we know the state of each object in graph (new/modified/unchanged/deleted)? Because detached objects is not tracked the only reliable it would is to reload the object graph form DB, and as I state D before I don ' t want to does that because of the performance.

We can use simple convention. If ID > 0 object is modified, and if id = 0 Then object is new. This was pretty simple convention, with drawbacks:

    • We can ' t detect unchanged objects so we'll be a saving to DB unchanged data. On the bright side these object graphs should is not being that big so this should is performance issue.
    • Deleting objects must is handled with custom logic. e.g. have something like Order.deletedorderlines collection.

In order to read Id value when attaching objects, all entities'll implement IEntity interface.

Collapse | Copy Code
 Public Interface ientity{     longget;}}  
Ignoring referent data

Each object graph can contain referential (read-only) data. If we are saving Order, we might has products and Customer objects in graph but we know so we don ' t want To save them in DB. We know that we should save only order and order Lines. On the other hand EF doesn ' t know. This is the IS-a-Attachbyidvalue accepts array of child types that should being attached for saving along with Order. All objects in graph that is not root nor is the child Type would be attached to context, but would be marked as unchanged So they won ' t is saved to DB.

To save with order (without order Lines) we should call:

Collapse | Copy Code
null); Mycontext.savechanges ();  

So to save order and order Lines we should call:

Collapse | Copy Code
New typeof

OFF course above hashset<type> can be cached in static field to avoid calling typeof on every object attaching.

Collapse | Copy Code
Private Static ReadOnly New typeof (Orderline)}; ... mycontext.attachbyidvalue (Order, orderchildtypes); Mycontext.savechanges ();   
The Final SolutionCollapse | Copy Code
/// <summary> ///attaches entity graph to context using the entity ID to determinate if entity is new or modified.///If Id is zero then the entity is treated as NEW and otherwise it is treated as modified.///If We want to save more than just root entity than child types must is supplied.///If entity in graph was not root nor the child type it would be attached and not saved///(It'll be treated as unchanged)./// </summary> /// <param name= "context">the context. < / param > /// <param name= "rootentity">the root entity. < / param > /// <param name= "childtypes">The child types this should be saved With root entity. < / param >  Public Static voidAttachbyidvalue<tentity> ( ThisDbContext context, TEntity rootentity, hashset<type> childtypes) where TEntity:class, ientity{//Mark Root entity as added    //This action adds whole graph and marks each entity in it as addedContext. Set<tentity> (). ADD (rootentity);//In case root entity have ID value mark it as modified (otherwise it stays added)    if(Rootentity.id! =0) {context. Entry (rootentity).    state = entitystate.modified; }//traverse all entities in context (hopefully they is all part of the graph we just attached)    foreach(varEntryinchContext. Changetracker.entries<ientity> ()) {//we are only interested in graph we have just attached        //And We know they is all marked as Added        //and we'll ignore root entity because it is already resolved correctly        if(Entry. state = = entitystate.added && entry. Entity! = rootentity) {//If no child types is defined for saving then just mark all entities as unchanged)            if(Childtypes = =NULL|| Childtypes.count = =0) {entry.            state = entitystate.unchanged; }Else{//Request object Type from context because we might got reference to dynamic proxy                //and we wouldn ' t want to handle Type of dynamic proxyType EntityType = Objectcontext.getobjecttype (entry. Entity.gettype ());//If type is not a child of type than it should not being saved so mark it as unchanged                if(!childtypes.contains (EntityType)) {entry.                state = entitystate.unchanged; }Else if(Entry. Entity.id! =0)                {//If entity should is saved with root entity                    //than if it has ID mark it as modified                    //else leave it marked as addedEntry.                state = entitystate.modified; }            }        }    }}
One gotcha

As I explained earlier, EF insists, all existing entities should has unique primary keys and this is what you Canno T attach to DbContext-unchanged objects of same type with same Id. This shouldn ' t is the case in general but I had found one edge case where it might occur. Let's say we is loading order, order Lines and products and we have a different order Lines pointing to same Pr Oduct . Normally EF would set reference to same Product object to these Order Lines unless is loading your data using Asnotrac King to get better performance in which case each Order line gets reference to separate Product object that's equal by Al L values. I didn ' t find documentation of this behavior anywhere, I had discovered by accident what struggling to attach objects to D Bcontext.

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.