Want to win a PS4? Go Premium and enter to win our High-Tech Treats giveaway. Enter to Win

x
?
Solved

Entity Framework 4 POCO update with disconnected objects

Posted on 2011-03-14
7
Medium Priority
?
1,973 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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
Visualize your virtual and backup environments

Create well-organized and polished visualizations of your virtual and backup environments when planning VMware vSphere, Microsoft Hyper-V or Veeam deployments. It helps you to gain better visibility and valuable business insights.

 

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 2000 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

Tech or Treat! - Giveaway

Submit an article about your scariest tech experience—and the solution—and you’ll be automatically entered to win one of 4 fantastic tech gadgets.

Question has a verified solution.

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

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…
International Data Corporation (IDC) prognosticates that before the current the year gets over disbursing on IT framework products to be sent in cloud environs will be $37.1B.
In this video, Percona Solution Engineer Dimitri Vanoverbeke discusses why you want to use at least three nodes in a database cluster. To discuss how Percona Consulting can help with your design and architecture needs for your database and infras…
Are you ready to place your question in front of subject-matter experts for more timely responses? With the release of Priority Question, Premium Members, Team Accounts and Qualified Experts can now identify the emergent level of their issue, signal…

597 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