2010-05-07 14 views
15

Tengo una relación de varios a varios entre Issues y Scopes en mi contexto de EF. En ASP.NET MVC, aparece un formulario de edición que permite al usuario editar un problema en particular. En la parte inferior del formulario, hay una lista de casillas de verificación que les permite seleccionar qué ámbitos se aplican a este problema. Al editar un problema, es probable que siempre tenga algunos ámbitos asociados con él: estos cuadros ya se verán. Sin embargo, el usuario tiene la oportunidad de verificar más ámbitos o eliminar algunos de los ámbitos controlados actualmente. Mi código se veía algo como esto para salvar sólo el tema de:Entidad de actualización de Entity Framework junto con entidades secundarias (agregar/actualizar según sea necesario)

  using (var edmx = new MayflyEntities()) 
      { 
       Issue issue = new Issue { IssueID = id, TSColumn = formIssue.TSColumn }; 
       edmx.Issues.Attach(issue); 

       UpdateModel(issue); 

       if (ModelState.IsValid) 
       { 
        //if (edmx.SaveChanges() != 1) throw new Exception("Unknown error. Please try again."); 
        edmx.SaveChanges(); 
        TempData["message"] = string.Format("Issue #{0} successfully modified.", id); 
       } 
      } 

lo tanto, cuando intento agregar en la lógica de salvar la alcances asociado, he intentado varias cosas, pero en última instancia, esto es lo que hizo el más sentido para mí:

  using (var edmx = new MayflyEntities()) 
      { 
       Issue issue = new Issue { IssueID = id, TSColumn = formIssue.TSColumn }; 
       edmx.Issues.Attach(issue); 

       UpdateModel(issue); 

       foreach (int scopeID in formIssue.ScopeIDs) 
       { 
        var thisScope = new Scope { ID = scopeID }; 
        edmx.Scopes.Attach(thisScope); 
        thisScope.ProjectID = formIssue.ProjectID; 
        if (issue.Scopes.Contains(thisScope)) 
        { 
         issue.Scopes.Attach(thisScope); //the scope already exists 
        } 
        else 
        { 
         issue.Scopes.Add(thisScope); // the scope needs to be added 
        } 
       } 

       if (ModelState.IsValid) 
       { 
        //if (edmx.SaveChanges() != 1) throw new Exception("Unknown error. Please try again."); 
        edmx.SaveChanges(); 
        TempData["message"] = string.Format("Issue #{0} successfully modified.", id); 
       } 
      } 

Pero, por desgracia, que acaba de lanza la siguiente excepción:

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. 

¿Qué estoy haciendo mal?

Respuesta

12

Los stubs generalmente solo son efectivos para las relaciones 1-*. *-* relaciones presentan un conjunto diferente de desafíos.

Es decir, cuando une ambos extremos - a diferencia de 1-* - todavía no tiene idea de si la relación ya existe o no.

Por lo que significa que este código:

if (issue.Scopes.Contains(thisScope)) 

probablemente va a volver cada vez que falsa.

Lo que quiero hacer es lo siguiente:

edmx.Issues.Attach(issue); 
UpdateModel(issue); 
// or ctx.LoadProperty(issue, "Scopes") if it is a POCO class. 
issue.Scopes.Load(); // hit the database to load the current state. 

Ahora tiene que averiguar lo que hay que añadir & retirar del tema.Alcances. Puede hacer esto comparando basado en ID.

es decir, si tiene un conjunto de ID de ámbito que desee tener en relación con la emisión (relatedScopes)

A continuación, este código se resuelve qué añadir y qué quitar.

int[] toAdd = relatedScopes.Except(issue.Scopes.Select(s => s.ID)).ToArray(); 
int[] toRemove = issue.Scopes.Select(s => s.ID).Except(relatedScopes).ToArray(); 

Ahora, para toadd hacer esto:

foreach(int id in toAdd) 
{ 
    var scope = new Scope{Id = id}; 
    edmx.Scopes.Attach(scope); 
    issue.Scopes.Add(scope); 
} 

Y para cada ámbito tiene que quitar

foreach(int id in toRemove) 
{ 
    issue.Scopes.Remove(issue.Scopes.Single(s => s.ID == id)); 
} 

Por ahora deben formarse las relaciones correctas.

Esperanza esto ayuda

Alex

Microsoft

+1

¡Eso es perfecto! Y mirando SQL Profiler, es solo una llamada "extra" al DB para .Load(), pero una manera mucho más limpia de agregar/eliminar que la forma en que solía hacerlo a mano con procedimientos almacenados. ¡Gracias! – Jorin

+0

James buena respuesta. Este es un problema común en muchas aplicaciones (efectivamente agregar/eliminar etiquetas). ¿No es hora de que una función de nivel del sistema haga esto sin una pantalla llena de código? Algo así como * issue.Scopes.ReplaceWith (myScopes); * – TFD

+0

@TFD, sí, te escucho. En su defecto, el equipo de EF tiene muchos en su plato, un método de extensión simple haría el truco, ¿verdad? –

0

Advertencia, esto es solo de la parte superior de mi cabeza, no lo intenté.

No creo que pueda establecer una clave foránea como lo hace con ProjectID.

necesita recuperar el proyecto y agregarlo al alcance. Propiedad de navegación del proyecto. EF se encargará de las relaciones cuando lo insertes.

Ahora, de nuevo, no lo intenté y podría estar equivocado, pero vale la pena intentarlo. Quizás esto ayude a ponerlo en camino ...

+0

no, no lo creo. Cambié la línea 'thisScope.ProjectID = formIssue.ProjectID;' a 'thisScope.Project = issue.Project;' y aún obtuve el mismo error. ¿Es eso lo que querías decir? – Jorin

+0

Ok, lo intentaré solo y publicaré el código que se me ocurrió si no resolvieras el problema tú solo. –

+0

no, me he agotado probando diferentes opciones. En mi caso de prueba, tengo 3 ámbitos, 2 de los cuales ya están asociados con este problema. Lo hago para que los 3 se comprueben en mi formulario, por lo que los 2 existentes deberían actualizarse y 1 debería estar asociado con el problema. Obtengo resultados variados según lo que intento, pero nada que haga lo que se supone que debe hacer. Extrañamente, con el método '.Add', alguna vez trata de agregar un nuevo alcance a la base de datos en lugar de simplemente" agregar "una nueva asociación con este problema, pero no puedo entender cómo hacer que funcione correctamente. . – Jorin

0

** Descargo de responsabilidad: Soy relativamente nuevo en EF, tome mi respuesta con un balde de sal.

Si se trata de un formulario de edición, creo que su objeto de problema no debería ser un problema "nuevo", debería sacar el problema del almacén de datos. Por lo que puedo ver al hacer:

Issue issue = new Issue { IssueID = id, TSColumn = formIssue.TSColumn }; edmx.Issues.Attach(issue); estás creando efectivamente un Nuevo Problema con el Id del que estás tratando de editar.

Una vez más, estoy buscando maneras de darle sentido a EF. A veces extraño mis declaraciones SQL.

+0

Gracias, pero no. Como dije, la parte Issue funciona bien, son solo las entidades secundarias que parece que no puedo actualizar. lo "nuevo" que ve allí es solo una forma de crear una entidad de código auxiliar en lugar de usar EntityKey. Ver este artículo: http://blogs.msdn.com/alexj/archive/2009/06/19/tip-26-how-to-avoid-database-queries-using-stub-entities.aspx – Jorin

+1

¡Ooohhh guapa! Voy a tener que probar esta cosa de la entidad de código auxiliar, se reducirá en una gran cantidad de consultas innecesarias. ¡Gracias! –

Cuestiones relacionadas