web-dev-qa-db-fra.com

Entity Framework: comment résoudre "La contrainte FOREIGN KEY peut provoquer des cycles ou des chemins de cascade multiples"?

il y a beaucoup de questions sur ce problème, mais je n'ai pas pu résoudre mon cas. quelqu'un peut-il jeter un oeil à ceci:

J'ai une table Office qui a une relation un-plusieurs avec les tables Doctor et Secretary. Les deux dernières tables sont dérivées de la table Employee qui a une relation de clé primaire partagée avec la table Users prédéfinie créée par sqlmembershipprovider. Il semble qu'il existe une relation plusieurs-plusieurs entre la table Users et la table Roles que je n'ai pas en main.

Mon problème était de créer une relation (zéro, un) n) entre ma table Employee et cette table Users que j'ai terminée avec une relation de clé primaire partagée entre eux et l'erreur soulevée, puis. (Existe-t-il une meilleure solution à ce problème?)

--- (voici l'erreur:

L'introduction de la contrainte FOREIGN KEY 'FK_dbo.aspnet_UsersInRoles_dbo.aspnet_Users_UserId' sur la table 'aspnet_UsersInRoles' peut provoquer des cycles ou plusieurs chemins de cascade. Spécifiez ON DELETE NO ACTION ou ON UPDATE NO ACTION, ou modifiez d'autres contraintes FOREIGN KEY. Impossible de créer une contrainte. Voir les erreurs précédentes.

voici mes codes et codes d'adhésion après reverse engineering:

public class Office
{
    public Office()
    {
        this.Doctors = new HashSet<Doctor>();
        this.Secretaries = new HashSet<Secretary>();
    }

    [Key]
    public System.Guid OfficeId { get; set; }
    public virtual ICollection<Doctor> Doctors { get; set; }
    public virtual ICollection<Secretary> Secretaries { get; set; }
}

public class Employee
{
    [Key, ForeignKey("User")]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public System.Guid Id { get; set; }
    public string Name { get; set; }

    [ForeignKey("Office")]
    public System.Guid OfficeId { get; set; }

    // shared primary key 
    public virtual aspnet_Users User { get; set; }

    public virtual Office Office { get; set; }
}

public class Doctor :Employee
{
    public Doctor()
    {
        this.Expertises = new HashSet<Expertise>();
    }
    //the rest..    
    public virtual ICollection<Expertise> Expertises { get; set; }
}

public class Secretary : Employee
{
    // blah blah
}

public class aspnet_Users
{
    public aspnet_Users()
    {
        this.aspnet_Roles = new List<aspnet_Roles>();
    }

    public System.Guid ApplicationId { get; set; }
    public System.Guid UserId { get; set; }
    //the rest..
    public virtual aspnet_Applications aspnet_Applications { get; set; }
    public virtual ICollection<aspnet_Roles> aspnet_Roles { get; set; }
}

public class aspnet_Roles
{
    public aspnet_Roles()
    {
        this.aspnet_Users = new List<aspnet_Users>();
    }

    public System.Guid ApplicationId { get; set; }
    public System.Guid RoleId { get; set; }
    //the rest..
    public virtual aspnet_Applications aspnet_Applications { get; set; }
    public virtual ICollection<aspnet_Users> aspnet_Users { get; set; }
}

EDIT: et les relations vont plus loin, il existe une relation multiple entre Users table et Applications table, également entre Roles et Applications aussi.

35
Blazi

Vous pouvez utiliser l'API couramment pour spécifier les actions suggérées par le message d'erreur.

Dans votre contexte:

protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<aspnet_UsersInRoles>().HasMany(i => i.Users).WithRequired().WillCascadeOnDelete(false);
}

Notez que vous n'avez pas inclus la définition de la table aspnet_UsersInRoles donc ce code peut ne pas fonctionner.

Une autre option consiste à supprimer tous les CASCADE DELETES en ajoutant ceci

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

Si vous avez besoin de plus d'informations sur la configuration des relations avec l'API fluide, je suggère http://msdn.Microsoft.com/en-US/data/jj59162

66
Charles

Vous pouvez également modifier votre classe de migration. Dans mon cas, dans la classe de migration était:

CreateTable(
    "dbo.Spendings",
    c => new
        {
          SpendingId = c.Int(nullable: false, identity: true),
          CategoryGroupId = c.Int(nullable: false),
          CategoryId = c.Int(nullable: false),
          Sum = c.Single(nullable: false),
          Date = c.DateTime(nullable: false),
          Comment = c.String(),
          UserId = c.String(),
          LastUpdate = c.String(),
        })
    .PrimaryKey(t => t.SpendingId)
    .ForeignKey("dbo.Categories", t => t.CategoryId, cascadeDelete: true)
    .ForeignKey("dbo.CategoryGroups", t => t.CategoryGroupId, cascadeDelete: true)
    .Index(t => t.CategoryGroupId)
    .Index(t => t.CategoryId);

Après avoir supprimé "cascadeDelete: true", Update-Database fonctionne parfaitement.

OU comme false

 .ForeignKey("dbo.Categories", t => t.CategoryId, cascadeDelete: false)
21
feeeper
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
    }

Ajoutez ce code en contexte

7
Mano

Sur la base de vos commentaires, le problème est que vous utilisez le même PK dans les deux tableaux. Je changerais cela tout de suite, j'aurais les utilisateurs PK comme champ supplémentaire dans les employés ayant une relation FK avec les utilisateurs (un à un, je suppose). Et ajoutez un PK indépendant aux utilisateurs (Un autre guide pour respecter le reste de vos tables). Si vous souhaitez garantir que la nouvelle clé étrangère dans Employees (celle qui pointe vers les utilisateurs) est unique, vous pouvez toujours ajouter un index unique.

De cette façon, vous gardez votre structure tout de même et vous vous débarrassez du cycle.

3
Carlos Grappa

Juste un mise à jour:

Comme d'autres réponses le suggèrent, vous ne devez PAS effectuer d'action sur la suppression. La méthode mise à jour est la suivante pour Entityframework Core 1.0

table.ForeignKey(name: "FK_Cities_Regions_RegionId",
                 column: x => x.RegionId,
                 principalTable: "Regions",
                 principalColumn: "RegionId",
                 onDelete: ReferentialAction.NoAction);

REMARQUE: faites ReferentialAction.NoAction sur onDelete

1
Ahmad