c# wpf entity-framework dbcontext n-tier-architecture

c# - Entity Framework 6-usar mi getHashCode()



wpf entity-framework (1)

Es interesante y sorprendente que haya conseguido que su aplicación funcione de esta manera en EF5. EF siempre requiere solo una instancia de cualquier entidad. Si se agrega un gráfico de objetos y EF asume incorrectamente que ya está rastreando un objeto cuando de hecho está rastreando una instancia diferente, entonces el estado interno que EF está rastreando será inconsistente. Por ejemplo, el gráfico solo usa referencias y colecciones .NET, por lo que el gráfico seguirá teniendo varias instancias, pero EF solo rastreará una instancia. Esto significa que los cambios en las propiedades de una entidad pueden no detectarse correctamente y la reparación entre instancias también puede resultar en un comportamiento inesperado. Sería interesante saber si su código resolvió estos problemas de alguna manera o si simplemente sucedió que su aplicación no alcanzó ninguno de estos problemas y, por lo tanto, el seguimiento de estado no válido no importó para su aplicación.

El cambio que hicimos para EF6 hace que sea menos probable que una aplicación pueda hacer que el seguimiento del estado de EF llegue a un estado no válido, lo que podría causar un comportamiento inesperado. Si tiene un patrón inteligente para asegurarse de que el estado de seguimiento sea válido y rompamos con EF6, sería genial si pudiera presentar un error con una reproducción completa en http://entityframework.codeplex.com/ .

Hay una cierta cantidad de antecedentes por los que pasar por esto. ¡Por favor, tengan paciencia conmigo!

Tenemos una aplicación WPF de n niveles que utiliza EF: cargamos los datos de la base de datos a través de dbContext en las clases de POCO. El dbContext se destruye y el usuario puede editar los datos. Usamos "pintura de estado" como lo sugiere Julie Lerman en su libro "Programming Entity Framework: DBContext", de modo que cuando agregamos la entidad raíz a un nuevo dbContext para guardar, podemos establecer si cada entidad secundaria se agrega, modifica o no cambia, etc. .

El problema que tuvimos cuando hicimos esto por primera vez (¡en noviembre de 2012!) Fue que si la entidad raíz que estamos agregando a dbContext tiene múltiples instancias de la misma entidad secundaria (es decir, un registro de "Tarea" vinculado a un usuario, con "Historias de estado" también vinculadas al mismo usuario) el proceso fallaría porque, aunque las entidades secundarias fueran las mismas (de la misma fila de la base de datos), se les asignaron diferentes códigos hash, por lo que EF los reconoció como objetos diferentes.

Arreglamos esto (¡en diciembre de 2012!), Anulando GetHashCode en nuestras entidades para devolver el ID de la base de datos si la entidad provenía de la base de datos, o un número negativo único si la entidad aún no está guardada. Ahora, cuando agregamos la entidad raíz al dbContext, fue lo suficientemente inteligente como para darse cuenta de que la misma entidad secundaria se está agregando más de una vez y se trató correctamente. Esto ha estado funcionando bien desde diciembre de 2012 hasta que cambiamos a EF6 la semana pasada ...

Una de las nuevas "características" con EF6 es que ahora utiliza sus propios métodos Equals y GetHashCode para realizar tareas de seguimiento de cambios, ignorando cualquier anulación personalizada. Consulte: http://msdn.microsoft.com/en-us/magazine/dn532202.aspx (busque "Menos interferencia con su estilo de codificación"). Esto es excelente si espera que EF administre el seguimiento de cambios, pero en una aplicación de n-niveles desconectada no queremos esto y, de hecho, esto rompe nuestro código que ha estado funcionando bien durante más de un año.

Esperemos que esto tenga sentido.

Ahora, la pregunta: ¿Alguien sabe de alguna manera que podamos decirle a EF6 que use los métodos de NUESTRO CÓDIGO HACER E IGUAL como lo hizo en EF5, o alguien tiene una mejor manera de lidiar con agregar una entidad raíz a un dbContext que tenga entidades hijas duplicadas? ¿En él para que EF6 esté contento con él?

Gracias por cualquier ayuda. Lo siento por el largo post.

ACTUALIZADO Habiendo hurgado en el código EF, parece que el código hash de InternalEntityEntry (dbEntityEntry) solía configurarse obteniendo el código hash de la entidad, pero ahora en EF6 se recupera utilizando RuntimeHelpers.GetHashCode (_entidad) Se ignora el código hash en la entidad. Así que supongo que obtener EF6 para usar nuestro código de hash está fuera de discusión, así que tal vez deba concentrarme en cómo agregar una entidad al contexto que potencialmente ha duplicado entidades secundarias sin molestar a EF. ¿Alguna sugerencia?

ACTUALIZACIÓN 2 Lo más molesto es que este cambio en la funcionalidad se informa como algo bueno, y no, como lo veo, ¡un cambio importante! Seguramente si ha desconectado entidades y las ha cargado con .AsNoTracking () para el rendimiento (y porque sabemos que las vamos a desconectar, entonces ¿por qué molestarse en rastrearlas) entonces no hay razón para que dbContext anule nuestro método getHashcode?

ACTUALIZACIÓN 3 Gracias por todos los comentarios y sugerencias, ¡muy apreciado! Después de algunos experimentos, parece estar relacionado con .AsNoTracking (). Si carga los datos con .AsNoTracking (), las entidades secundarias duplicadas son objetos separados en la memoria (con diferentes hashcodes), por lo que existe un problema al pintar el estado y guardarlos más tarde. Solucionamos este problema anteriormente al anular los códigos hash, de modo que cuando las entidades se vuelven a agregar al contexto de guardado, las entidades duplicadas se reconocen como el mismo objeto y solo se agregan una vez, pero ya no podemos hacerlo con EF6. Así que ahora necesito investigar más a fondo por qué usamos .AsNoTracking () en primer lugar. Otro pensamiento que tengo es que tal vez el rastreador de cambios de EF6 solo debería usar su propio método de generación de código de hash para las entradas que está rastreando activamente. ¿Si las entidades se han cargado con .AsNoTracking () tal vez debería usar el código de hash de la entidad subyacente?

ACTUALIZACIÓN 4 Por lo tanto, ahora hemos comprobado que no podemos continuar utilizando nuestro enfoque (códigos hash anulados y .AsNoTracking) en EF6, ¿cómo debemos administrar las actualizaciones de las entidades desconectadas? He creado este ejemplo simple con blogposts / comentarios / autores:

En esta muestra, quiero abrir blogpost 1, cambiar el contenido y el autor, y guardar de nuevo. He intentado 3 enfoques con EF6 y no puedo hacerlo funcionar:

BlogPost blogpost; using (TestEntities te = new TestEntities()) { te.Configuration.ProxyCreationEnabled = false; te.Configuration.LazyLoadingEnabled = false; //retrieve blog post 1, with all comments and authors //(so we can display the entire record on the UI while we are disconnected) blogpost = te.BlogPosts .Include(i => i.Comments.Select(j => j.Author)) .SingleOrDefault(i => i.ID == 1); } //change the content blogpost.Content = "New content " + DateTime.Now.ToString("HH:mm:ss"); //also want to change the author from Fred (2) to John (1) //attempt 1 - try changing ID? - doesn''t work (change is ignored) //blogpost.AuthorID = 1; //attempt 2 - try loading the author from the database? - doesn''t work (Multiplicity constraint violated error on Author) //using (TestEntities te = new TestEntities()) //{ // te.Configuration.ProxyCreationEnabled = false; // te.Configuration.LazyLoadingEnabled = false; // blogpost.AuthorID = 1; // blogpost.Author = te.Authors.SingleOrDefault(i => i.ID == 1); //} //attempt 3 - try selecting the author already linked to the blogpost comment? - doesn''t work (key values conflict during state painting) //blogpost.Author = blogpost.Comments.First(i => i.AuthorID == 1).Author; //blogpost.AuthorID = 1; //attempt to save using (TestEntities te = new TestEntities()) { te.Configuration.ProxyCreationEnabled = false; te.Configuration.LazyLoadingEnabled = false; te.Set<BlogPost>().Add(blogpost); // <-- (2) multiplicity error thrown here //paint the state ("unchanged" for everything except the blogpost which should be "modified") foreach (var entry in te.ChangeTracker.Entries()) { if (entry.Entity is BlogPost) entry.State = EntityState.Modified; else entry.State = EntityState.Unchanged; // <-- (3) key conflict error thrown here } //finished state painting, save changes te.SaveChanges(); }

Si usa este código en EF5, utilice nuestro enfoque existente de agregar .AsNoTracking () a la consulta original.

blogpost = te.BlogPosts .AsNoTracking() .Include(i => i.Comments.Select(j => j.Author)) .SingleOrDefault(i => i.ID == 1);

..y sobreescribiendo GetHashCode y Equals en las entidades: (por ejemplo, en la entidad BlogPost) ..

public override int GetHashCode() { return this.ID; } public override bool Equals(object obj) { BlogPost tmp = obj as BlogPost; if (tmp == null) return false; return this.GetHashCode() == tmp.GetHashCode(); }

..todos los tres enfoques en el código ahora funcionan bien.

Por favor, ¿puedes decirme cómo lograr esto en EF6? Gracias