views:

146

answers:

2

Hi everyone,

I'm having a little trouble with my Entity add functionality.

Setup

For simplicity, I have in DB with 4 tables, Customer, Address, Area, Location.

  • Customer stores basic customer details ie. Id, Name, Username, Password, etc.
  • Address stores street name, street number, etc.
  • Area stores area name and area code.
  • Location stores location name and location code.

The relationship is as follows Customer *--->1 Address *--->1 Area *--->1 Location

In my application, I would have a function to create a new customer. There are also some textboxes available for the address information. The Area and Location information can be selected from a list of available Areas and Locations which I have pre-fetched from the database and populated into a ComboBox.

My application does not use the ADO.net EF directly, but via a WCF data service call. The service sits on the SQL server hosted by IIS. Here is the service call to retrieve Customers in the Business Logic Layer

/// <summary>
/// The get all customers.
/// </summary>
/// <returns>
/// </returns>
public static List<Customer> GetAll()
{
    using (var context = new CustomerDataEntities())
    {
        // I may be missing something here.. Possibly a call to Detach().
        // I have been ommitting it because a call to Detach()
        // tends to cut away any .Include() from the data. Maybe there is another way?
        return context.CustomerSet.ToList();
    }
}

As you can see. This will indefinitely lead to multiple instances of the Context (as mentioned by Craig Stuntz that this is a mistake).

Then in the WCF data service.

// IDataService.cs
[OperationContract]
List<Customer> GetAllCustomers();

// DataService.svc.cs
public List<Customer> GetAllCustomers()
{
    return CustomerBLL.GetAll();
}

The application proceeds to use the list of Customers from the above retrieval, but ops to Create a new Customer like so:

private Location newLocation;
private Area newArea;
private Address newAddress;
private Customer newCustomer;

// Code omitted for clarity..

public void CreateCustomer()
{
    newLocation = new Location();
    newArea = new Area{Location = newLocation};
    newAddress = new Address{Area = newArea};
    newCustomer = new Customer{Address = newAddress};
}

Where Location, Area, Address and Customer is the reference to the ADO.net EF model classes. The newLocation and newArea is then modified when the user selects a Location or Area from a list in the ComboBox like so:

private Location _selectedLocation;
public Location SelectedLocation
{
    get { return _selectedLocation; }
    set {
            _selectedLocation = value;
            newLocation = _selectedLocation;
        }
}

SelectedLocation is data bound to the combo box SelectedItem of the locations combo box. A similar process happens to newArea.

When the User clicks save the following happens:

public bool SaveCustomer(Customer customer)
{
    if(customer == null) return false;

    var dataService = new DataService.DataServiceClient();
    try
    {
        if (customer.ID > 0) dataService.UpdateCustomer(customer);
        else dataService.CreateCustomer(customer);
    }
    catch (Exception e)
    {
        Trace.WriteLine(e.Message);
        return false;
    }

    return true;
}

The method that gets called in the data service is like so.

/// <summary>
/// The create.
/// </summary>
/// <param name="customer">
/// The customer.
/// </param>
public static bool Create(Customer customer)
{
    if (customer== null) return false;

    // Once again, a new instance of the Context..
    using (var context = new CustomerDataEntities())
    {
        try
        {
            context.AddToCustomerSet(customer);
            context.SaveChanges();
        }
        catch (Exception e)
        {
            return false;
        }

        return true;
    }
}

The Problem

This would throw an error:

The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.

This is because the Customer.Address.Area already exists in the context and its asking me to use context.Attach() first. I've tried context.Attach() to no avail. What is the approach I should be taking to takle this problem?

A: 

Hi, could you include the code where you construct the new customer?

madC
+1  A: 

Rather than attempting a specific fix to this one issue, I would urge you to come up with a general strategy for the lifetimes of your ObjectContexts. I can't give you a single "best practice" for this, because it depends upon your application. For example, I mainly do ASP.NET MVC applications, so the lifetime for my ObjectContexts is always one context which lasts for exactly one request.

The general issue here is that the Entity Framework will expect that any objects in an object graph (in other words, objects that refer to each other, directly or in directly) will be part of a single ObjectContext. If, for example, you have an object called Area, which is attached to a certain ObjectContext and an object called Customer which is not attached to any ObjectContext (in other words, it's detached), and you change Customer so that it indirectly refers to Area via Address, then if you look at the EntityState of Customer, you will see that it is now "added". Attempting to add it to a different ObjectContext via AddToCustomerSet will throw the error you report, because an entity cannot live in two ObjectContexts. On the other hand, calling SaveChanges on the former context will result in the Customer being added to the database, without ever calling AddToCustomerSet at all, because it's already added to the set implicitly.

This might sound complicated, or difficult to manage. But you will find that nearly all of this complication just goes away if you can find a way to use a single ObjectContext. There is no rule against using multiple ObjectContexts, but if you are new to Entity Framework, I would urge you to not do this until later, when you are much more comfortable with EF.

Craig Stuntz
Thanks again Craig, this is definitely the kind of direction I need. I have also found a solution to this problem from CodeProject http://www.codeproject.com/KB/architecture/attachobjectgraph.aspx. This solves the problem with trying to Add/Modify data with a mix of Attached and Detached objects referencing to one another.
Tri Q