Solved

Entity Framework 4 POCO update with disconnected objects

Posted on 2011-03-14
7
1,956 Views
Last Modified: 2012-08-13
Hi experts,
I have something still not clear about updates in EF 4 with POCO classes (disconnected object).

I have let's say an objet called user and a graph like this:

user.FirstName (property)
user.Orders (collection of orders)
user.Profile (object profile)
user.Friends (collection of friends)
and so on

I can add or delete, but my problem starts when I need to update.
What I usually do is to retieve the original entity from the context (with the navigation properties) and then I iterate through the object graph in order to manually update the properties for all the navigation properties as well.
Sometime happens that the object contains some object to add and not to update.
For istance the user can contains a new order, and so I need to add the new order and not update (But for the user itself it's an update).
Does exist a flexible way to have a generic function where I can pass the user or even better a generic entity (that can contains elements to update and/or elements to add) and EF understands what to add or what to update?

something like

 public ResultObject UpdateUser(User user)
{
       //context analyzes the object user with navigation properties (collection as well)
       //and add or updates everything automatically
}

If doesn't exist a way for that can someone explain me the concept for updating disconnected object with navigation properties to update or add?

P.S. I can't use self tracking entities

Thanks in advance
0
Comment
Question by:xtremereality
  • 3
  • 2
  • 2
7 Comments
 
LVL 21

Expert Comment

by:Craig Wagner
Comment Utility
I'm not sure what you mean by a generic function. Your method should simply retrieve the User and related child objects (using .Include) so the whole (existing) object graph is in memory. Loop over the incoming object graph and if the object is already in the collection you retrieved from the database you transfer the property values over and if not you create a new object and add it to the collection. Part of the power of EF is that it will figure out which objects are new and which are existing to be updated.
0
 
LVL 21

Expert Comment

by:Craig Wagner
Comment Utility
Pseudo code would look something like the following, but I'd probably break it into multiple smaller methods for readability and maintainability (e.g. one method to handle the Orders, one method to handle the Profile, etc).
public ResultObject UpdateUser(User user)
{
    User existingUser = dbContext.Users
                            .Include("Orders")
                            .Include("Profile")
                            .Include("Friends")
                            .Where( u => u.UserId == user.UserId )
                            .SingleOrDefault();
    if( existingUser != null )
    {
        existingUser.FirstName = user.FirstName;
        // etc for rest of user properties
        
        if( existingUser.Profile == null )
        {
            // user doesn't have a profile, create a new one
            // and add it to graph
            Profile existingUser.Profile = new Profile();
        }

        existingUser.Profile.Prop1 = user.Profile.Prop1;
        // etc for rest of user properties

        foreach( Order order in user.Orders )
        {
            existingOrder = Orders
               .Where( o => o.OrderId == order.OrderId ).SingleOrDefault();
            if( existingOrder == null )
            {
                existingOrder = new Order();
                existingUser.Orders.Add( existingOrder );
            }

            existingOrder.prop1 = order.Prop1;
            // copy rest of order properties

            // Do a loop similar to the above for Friends and
            // any other collections.
        }
    }
    else
    {
        // everything is new, add object graph to context
    }

    dbContext.SaveChanges();
}

Open in new window

0
 

Author Comment

by:xtremereality
Comment Utility
Thanks for the answer Craig.
What I mean is this: It's not clear to me how to perform an update in EF4 with poco in a disconnected scenario.
Let me give you an example:

On the client:

var user = repository.GetUser(userId);

this user contains navigation properties (and some of them are collections).

var friend = user.Friends.FirstOrDefault();

friend.Something = foo;
user.Name = "new name";

var contact = new contact { something = foo };

user.Contacts.Add(contact);
user.Address.Street = "new street";

var result = repository.UpdateUser(user);


On the data access layer

function bool UpdateUser(User user)
{
      // How to perform updates of the user object?
      // it contains new elements as well as modified elements.
      // and it's disconnected
}

Hope is more clear what I mean and where I need some help to try to understand.

If I have to iterate through the object collection how can I know if an element of the collection is new or updated?

Thanks
0
What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 

Author Comment

by:xtremereality
Comment Utility
Sorry Craig I didn't see your coding 'cos I was writing my reply.
What you wrote is what I usually do (as I wrote on my original post).
But my real object has tons of properties and collections and I was wondering if there is a better and more simple way to perform the update. In this case I have to perform lots of queries to compare the old object with my new object in order to do the appropriate changes.
I saw on some website (and here is where my confusion starts) there are other methods to perform update, with detectChanges, attach object to the context...
But I didn't find an example that fits my needs.
0
 
LVL 21

Accepted Solution

by:
Craig Wagner earned 500 total points
Comment Utility
We use POCOs with Entity Framework as well, so we've run into these issues ourselves.

The problem with the Attach or AttachTo methods is that they object is attached to the context in an Unchanged state. We never did find a way to change the state of the object (and its properties) without going through each property and assigning it a value.

DetectChanges is automatically called when calling SaveChanges. It is just making sure that the POCO and the ObjectStateEntry held in the ObjectContext are in sync so EF knows what statements to generate.

What we ended up doing was writing a reflection-based method to copy the properties from one instance of an object to another. We have a common base class for all our POCOs (called BusinessContract). The method has to make sure it doesn't copy child object references or collection references. This would save you the tedious thisObject.Property1 = thatObject.Property1 code but you would still need to do the looping and creation of new objects if they aren't already in the collection. You could try adapting it to handle those conditions as well, but we tried for a couple of days and finally gave up because of the number of problems it was creating for us. Every time we thought we'd solved one problem it would cause another.

Realistically though, this is code you should only have to write once. It'll be tedious, but once it's written you're done with it.

public void OverwriteWith(object source, bool overwriteWithNulls = true)
{
    // If source is null, we are out of here
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }

    //get an array of all the properties on the business contract
    PropertyInfo[] sourceProperties = source.GetType().GetProperties();

    Type targetType = this.GetType();

    foreach (PropertyInfo sourceProperty in sourceProperties)
    {
        //get the value to copy
        object sourcePropertyValue = sourceProperty.GetValue(source, null);

        //if the value is null only perform copy if overwriteWithNulss is set to true
        if (overwriteWithNulls || (sourcePropertyValue != null))
        {
            //get the Base Type of the property. As long as it isn't a child business contract
            //we want to copy the value. 
            Type propertyType = sourceProperty.PropertyType;
            Type baseType = propertyType.BaseType;
            if (baseType != typeof(BusinessContract) &&
                (!propertyType.IsGenericType || propertyType.GetGenericTypeDefinition() != typeof(ICollection<>)))
            {
                PropertyInfo targetProperty = targetType.GetProperty(sourceProperty.Name);

                // Make sure the property name and type are identical on the source and target
                if (targetProperty != null &&
                    targetProperty.PropertyType == sourceProperty.PropertyType &&
                    targetProperty.CanWrite)
                {
                    targetProperty.SetValue(this, sourcePropertyValue, null);
                }
            }
        }
    }
}

Open in new window

0
 
LVL 17

Expert Comment

by:ToAoM
Comment Utility
Have you simply tried adding your existing Poco object to an empty collection in the datacontext

The second example on this page suggests it should work:
http://msdn.microsoft.com/en-us/library/dd456854.aspx

// Disable proxy object creation.
context.ContextOptions.ProxyCreationEnabled = false;

context.Users.Add(yourExistingPoco);

And then calling context.DetectChanges()
And then calling context.SaveChanges()

That should do the trick if I read the docs right...
0
 
LVL 17

Expert Comment

by:ToAoM
Comment Utility
Further reading contradicts my previous statement, but there are some helper methods. Read the following whole post for tips:
http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/fdc5c7ac-dcf8-45a8-ab11-2ffbc338b0e5/
0

Featured Post

Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

Join & Write a Comment

Use this article to create a batch file to backup a Microsoft SQL Server database to a Windows folder.  The folder can be on the local hard drive or on a network share.  This batch file will query the SQL server to get the current date & time and wi…
Ever needed a SQL 2008 Database replicated/mirrored/log shipped on another server but you can't take the downtime inflicted by initial snapshot or disconnect while T-logs are restored or mirror applied? You can use SQL Server Initialize from Backup…
It is a freely distributed piece of software for such tasks as photo retouching, image composition and image authoring. It works on many operating systems, in many languages.
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.

772 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now