Solved

Entity Framework 4 POCO update with disconnected objects

Posted on 2011-03-14
7
1,963 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
ID: 35137912
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
ID: 35138001
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
ID: 35138028
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
Optimizing Cloud Backup for Low Bandwidth

With cloud storage prices going down a growing number of SMBs start to use it for backup storage. Unfortunately, business data volume rarely fits the average Internet speed. This article provides an overview of main Internet speed challenges and reveals backup best practices.

 

Author Comment

by:xtremereality
ID: 35138109
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
ID: 35138784
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:Jesse Houwing
ID: 35140408
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:Jesse Houwing
ID: 35140444
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

MS Dynamics Made Instantly Simpler

Make Your Microsoft Dynamics Investment Count  & Drastically Decrease Training Time by Providing Intuitive Step-By-Step WalkThru Tutorials.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

SQL Server engine let you use a Windows account or a SQL Server account to connect to a SQL Server instance. This can be configured immediatly during the SQL Server installation or after in the Server Authentication section in the Server properties …
The article shows the basic steps of integrating an HTML theme template into an ASP.NET MVC project
Are you ready to implement Active Directory best practices without reading 300+ pages? You're in luck. In this webinar hosted by Skyport Systems, you gain insight into Microsoft's latest comprehensive guide, with tips on the best and easiest way…

730 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