Pregunta Relación entre muchas y muchas teclas izquierda y derecha volteadas después de la actualización de Entity Framework 5


Tengo un código que guarda una relación de muchos a muchos en el código. Funcionó bien con Entity Framework 4.1 pero después de actualizar a Entity Framework 5, está fallando.

Me aparece el siguiente error:

La instrucción INSERT entró en conflicto con la restricción FOREIGN KEY "FK_WebUserFavouriteEvent_Event". El conflicto ocurrió en la base de datos "MainEvents", tabla "dbo.Event", columna "Id".

Estoy usando entidades POCO con asignaciones personalizadas. El campo estándar y las correlaciones de relaciones de muchos a uno parecen funcionar bien.

ACTUALIZAR

Ok, entonces tengo SQL Profiler instalado y el diagrama se ha espesado ...

exec sp_executesql N'insert [dbo].[WebUserFavouriteEvent]([WebUserId], [EventId])
values (@0, @1)
',N'@0 int,@1 int',@0=1820,@1=14

Lo que significa:

WebUserId = @0 = 1820
EventId = @1 = 14

Lo interesante es que EF5 parece haber volteado las claves foráneas... el WebUserId debe ser 14 y el EventId debería ser 1820, no al revés como ahora.

Revisé el código de mapeo y estoy al 99%. Lo configuré todo correctamente. Ver API de Entity Framework Fluent - Relaciones artículo de MSDN para más información.

NOTA: También encontré que esto no está restringido para guardar tampoco, los SELECT también están rotos.

Aquí está todo el código relevante:

Capa de servicio

public void AddFavEvent(WebUser webUser, Event @event)
{
    webUser.FavouriteEvents.Add(@event);

    _webUserRepo.Update(webUser);
}

Repositorio

public void Update<T>(params T[] entities)
    where T : DbTable
{
    foreach (var entity in entities)
    {
        entity.UpdatedOn = DateTime.UtcNow;
    }

    _dbContext.SaveChanges();
}

NOTA: Estoy usando un 1 DataContext por enfoque de solicitud, por lo webUser y @event habría sido cargado desde el mismo contexto que el de la _webUserRepo.

Entidades (no te preocupes por las cosas de DbTable)

public class Event : DbTable
{
    //BLAH
    public virtual ICollection<WebUser> FavouriteOf { get; set; }
    //BLAH
}

public class WebUser : DbTable
{
    //BLAH
    public virtual ICollection<Event> FavouriteEvents { get; set; }
    //BLAH
}

Asignaciones

public class EventMapping : DbTableMapping<Event>
{
    public EventMapping()
    {
        ToTable("Event");
        //BLAH
        HasMany(x => x.FavouriteOf)
            .WithMany(x => x.FavouriteEvents)
            .Map(x =>
                     {
                         x.MapLeftKey("EventId");
                         x.MapRightKey("WebUserId");
                         x.ToTable("WebUserFavouriteEvent");
                     });
    }
}

public class WebUserMapping : DbTableMapping<WebUser>
{
    public WebUserMapping ()
    {
        HasMany(x => x.FavouriteEvents)
            .WithMany(x => x.FavouriteOf)
            .Map(m =>
                     {
                         m.MapLeftKey("WebUserId");
                         m.MapRightKey("EventId");
                         m.ToTable("WebUserFavouriteEvent");
                     });
    }
}

13
2017-08-30 08:19


origen


Respuestas:


Mirando esto, sospecho que el problema puede deberse al hecho de que mapea la misma relación dos veces. Y lo mapea en diferente orden.

Hice una prueba simple donde primero tracé la relación una vez:

    class Program
{
    static void Main(string[] args)
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
        var p = new Parent();
        var c = new Child();
        using (var db = new Context())
        {
            db.Parents.Add(new Parent());
            db.Parents.Add(p);

            db.Children.Add(c);
            db.SaveChanges();
        }

        using (var db = new Context())
        {
            var reloadedP = db.Parents.Find(p.ParentId);
            var reloadedC = db.Children.Find(c.ChildId);

            reloadedP.Children = new List<Child>();
            reloadedP.Children.Add(reloadedC);

            db.SaveChanges();
        }

        using (var db = new Context())
        {
            Console.WriteLine(db.Children.Count());
            Console.WriteLine(db.Children.Where(ch => ch.ChildId == c.ChildId).Select(ch => ch.Parents.Count).First());
            Console.WriteLine(db.Parents.Where(pa => pa.ParentId == p.ParentId).Select(pa => pa.Children.Count).First());
        }
    }
}

public class Parent
{
    public int ParentId { get; set; }
    public ICollection<Child> Children { get; set; }

}

public class Child
{
    public int ChildId { get; set; }
    public ICollection<Parent> Parents { get; set; }
}

public class Context : DbContext
{
    public Context() : base("data source=Mikael-PC;Integrated Security=SSPI;Initial Catalog=EFTest")
    {

    }

    public IDbSet<Child> Children { get; set; }
    public IDbSet<Parent> Parents { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Child>()
            .HasMany(x => x.Parents)
            .WithMany(x => x.Children)
            .Map(c =>
            {
                c.MapLeftKey("ChildId");
                c.MapRightKey("ParentId");
                c.ToTable("ChildToParentMapping"); 
            });

    }
}

Y luego cambié el OnModelCreating para ser:

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Child>()
            .HasMany(x => x.Parents)
            .WithMany(x => x.Children)
            .Map(c =>
            {
                c.MapLeftKey("ChildId");
                c.MapRightKey("ParentId");
                c.ToTable("ChildToParentMapping"); 
            });

        modelBuilder.Entity<Parent>()
           .HasMany(x => x.Children)
           .WithMany(x => x.Parents)
           .Map(c =>
           {
               c.MapLeftKey("ParentId");
               c.MapRightKey("ChildId");
               c.ToTable("ChildToParentMapping");
           });
    }

Lo que encontré y sospeché es que la primera ejecución genera este sql:

exec sp_executesql N'insert [dbo].[ChildToParentMapping]([ChildId], [ParentId])
values (@0, @1)
',N'@0 int,@1 int',@0=1,@1=2

En contraste con el segundo que genera:

exec sp_executesql N'insert [dbo].[ChildToParentMapping]([ParentId], [ChildId])
values (@0, @1)
',N'@0 int,@1 int',@0=1,@1=2

¿Ves los valores volteados? Aquí realmente cuenta la columna ChildId como ParentId. Ahora bien, esto no falla para mí, pero dejo que EF cree la base de datos, lo que significa que probablemente solo cambie los nombres de las columnas y si mirara las claves externas también las cambiaría. Si creó la base de datos manualmente, probablemente ese no sea el caso.

En resumen: tus asignaciones no son iguales y espero que una de ellas sea utilizada y esa probablemente sea incorrecta. En versiones anteriores, supongo que EF las recogió en diferente orden.

ACTUALIZAR: Me dieron un poco de curiosidad acerca de las claves externas y verifiqué el sql.

Desde el primer código:

ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Children_ChildId] FOREIGN KEY ([ChildId]) REFERENCES [dbo].[Children] ([ChildId]) ON DELETE CASCADE

Y desde el segundo código:

ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Children_ParentId] FOREIGN KEY ([ParentId]) REFERENCES [dbo].[Children] ([ChildId]) ON DELETE CASCADE

Ahora eso no es bueno. ParentId mapeado contra niños ciertamente no es lo que queremos.

Entonces, ¿el segundo mapeo está equivocado? No realmente porque veo lo que sucedió cuando eliminé el primero:

ALTER TABLE [dbo].[ChildToParentMapping] ADD CONSTRAINT [FK_dbo.ChildToParentMapping_dbo.Parents_ParentId] FOREIGN KEY ([ParentId]) REFERENCES [dbo].[Parents] ([ParentId]) ON DELETE CASCADE

De alguna manera, tener dos asignaciones parece estropear las cosas. Error o no, no sé.


13
2017-09-02 17:16



Puedo confirmar que esto es un error en EF5, aunque todavía no estoy seguro de cómo funcionó en 4.3.1.

El problema es que no estamos asociando correctamente las llamadas LeftKey / RightKey con sus propiedades de navegación correspondientes.

Archivaré un error de EF6 en nuestro sitio de proyecto CodePlex.

Para solucionarlo, creo que necesitará:

  1. Configure la asociación solo desde un lado. O,
  2. Cambie los nombres de las columnas LeftKey / RightKey para que sean iguales en ambas configuraciones.

Lo siento por los inconvenientes ocasionados.

ACTUALIZAR: Aquí está el error.


6
2017-09-06 20:37