Pregunta

Hi I am using entity framework 5. I have three models: Request,Regions,Tags. There is a many to many relationship between Request and Regions and Request and Tags. I followed this to create a wizard like form. so i have three viewmodels . One view form gets the basic info and the other two forms the tags (multiple select) and regions(multiple select) related to the request.

In order to pass the same request session to Respective actions I use this function to create and pass any specific request model instance to respective Actions

private Request GetRequest()
    {
        if (Session["request"] == null)
        {
            Debug.WriteLine("New Session Creation");
            Session["request"] = new Request();
        }
        Debug.WriteLine("SameSession");
        return (Request)Session["request"];
    }

I have a single instance of Entities cxt = new Entities(); in my controller I get the ids of selected regions and tags from a form and query the db in order to add them to the request object instance like so.

RegionActionResult

long val;
                foreach (var item in data.Regions)
                {
                    val = Convert.ToInt64(item);
                   request.Regions.Add(cxt.Regions.Single(r => r.Id == val));
                }

TagActionResult

long val;
                foreach (var item in data.Tags)
                {
                    val = Convert.ToInt64(item);
                    request.Tags.Add(cxt.Tags.Single(r => r.Id == val));
                }`enter code here`

Then I save the request here like so

FinalActionResult

Request_Log request = GetRequest();

            cxt.Request.Add(request); 
            cxt.SaveChanges(); 

Calling the final action to save the request raises the error An entity object cannot be referenced by multiple instances of IEntityChangeTracker. I am quite new to entity framework so I dont know what the issue is. I have only one instance of the context variable cxt which is use to fetch and add the regions and tags to the request object and then save with this same context so I am lost as to how i have more that one context here. I hope my question makes sense.

¿Fue útil?

Solución

If you create a context with Entities cxt = new Entities() in your controller you create a new ctx instance with each web request, especially you have then different context instances for the two wizard forms that select the regions and that select the tags. Therefore cxt.Regions.Single(...) and cxt.Tags.Single(...) retrieve Region and Tag entities with two different contexts and you add those entities to the request object graph. When you later add this request object with cxt.Request.Add(request) (probably even in a third ctx instance) EF notices that the related Regions and Tags have references to different contexts (usually the case if they are proxy objects due to some virtual properties) which is forbidden and the root of the exception.

It might be possible to solve the problem when you disable proxy creation before you load the regions and tags and then reattach the entities to the final context before you save. However, this is a very hacky solution in my opinion. I would very much prefer to hold anything else but entities in the Session object (for example some simple data objects or view models that just represent the data that have been posted back from the wizard forms) to avoid problems like this and then convert those data into entities only in the final action that saves to the database.

For example, store the needed data in three session values - one for each wizard form:

Session["request"] = data.SomeRequestData; // your basic request info
Session["regions"] = data.Regions; // your collection of region IDs
Session["tags"] = data.Tags; // your collection of tag IDs

And only in the final action you put the pieces together to an entity and you do it in a single context instance:

var request = new Request();

var requestData = Session["request"] as SomeRequestDataModel;

request.Property1 = requestData.Property1;
request.Property2 = requestData.Property2;
// etc., just copy the properties

long val;
var regionIDs = Session["regions"] as IEnumerable<string>;
foreach (var item in regionIDs)
{
    val = Convert.ToInt64(item);
    request.Regions.Add(cxt.Regions.Single(r => r.Id == val)); // or .Find(val)
}

var tagIDs = Session["tags"] as IEnumerable<string>;
foreach (var item in tagIDs)
{
    val = Convert.ToInt64(item);
    request.Regions.Add(cxt.Tags.Single(r => r.Id == val)); // or .Find(val)
}

cxt.Request.Add(request);
cxt.SaveChanges();

(I've left out the checks for null of Session["XXX"] for simplicity.) With this procedure you can be sure that all entities that are part of the final object graph are attached to the same context instance ctx and your exception won't occur.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top