EF 4.1 RC: странное каскадное удаление

Должен признаться, возможности EF 4.1 RC Codefirst, DataAnnotations и FluentAPI по-прежнему ошеломляют меня. Иногда я действительно не понимаю, что делаю ;-) См. следующие POCO:

public class Country
{
    [Key]
    public Guid ID { get; set; }

    [Required]
    public virtual Currency Currency { get; set; }
}

public class Currency
{
    [Key]
    public Guid ID { get; set; }

    public virtual ICollection<Country> Countries { get; set; }
}

Общая идея: у каждой страны должна быть валюта. Но валюту вообще не нужно привязывать к стране.

Если вы позволите EF создать соответствующую базу данных, отношение будет установлено как CASCADE DELETE по соглашению. Другими словами: если вы удаляете валюту, соответствующие страны также удаляются. Но в моем случае это не то, что я хочу.

Я придумал какой-то код в FluentAPI, чтобы отключить CASCADE DELETE:

modelBuilder.Entity<Country>()
            .HasRequired(cou => cou.Currency)
            .WithOptional()
            .WillCascadeOnDelete(false);

Я думал, что это означает: каждой стране нужна валюта. И этой валюте может быть назначено ноль, одна или несколько стран (необязательно). И всякий раз, когда я удаляю валюту, соответствующие страны (если они есть) НЕ будут удалены каскадом.

Удивительно, но данный подход по-прежнему будет каскадно удалять страну, если я удалю соответствующую валюту. Может ли кто-нибудь сказать мне, что мне не хватает?


person Ingmar    schedule 23.03.2011    source источник


Ответы (1)


Во-первых, вы указали валюту в качестве обязательного поля для страны, поэтому вы не можете удалить валюту. Вам нужно будет удалить [Обязательно].

Во-вторых, вашему конструктору моделей необходимо следующее:

modelBuilder.Entity<Country>()
            .HasRequired(cou => cou.Currency) //note optional, not required
            .WithMany(c=>c.Countries)         //define the relationship
            .WillCascadeOnDelete(false);

В-третьих, вам нужно явно удалить ссылку на объект, который вы удаляете, из его дочерних элементов:

 Currency c = context.Currencies.FirstOrDefault();

                c.Countries.Clear(); //these removes the link between child and parent

                context.Currencies.Remove(c);

                context.SaveChanges();

[EDIT] Поскольку я подозреваю, что что-то потеряно в переводе, найдите полный код, демонстрирующий, как будет работать некаскадное удаление.

public class Country{
  [Key]
  public Guid ID { get; set; }

  public virtual Currency Currency { get; set; }
}

public class Currency{
  [Key]
  public Guid ID { get; set; }

  public virtual ICollection<Country> Countries { get; set; }
}


public class MyContext : DbContext{
  public DbSet<Currency> Currencies { get; set; }
  public DbSet<Country> Countries { get; set; }

  protected override void OnModelCreating(DbModelBuilder modelBuilder){
    modelBuilder.Entity<Country>()
     .HasRequired(country => country.Currency)
     .WithMany(currency => currency.Countries)
     .WillCascadeOnDelete(false);
  }
}

class Program{
  static void Main(string[] args){
    Database.DefaultConnectionFactory = new   SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");

    Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());

    using (MyContext context1 = new MyContext()){
      Currency c = new Currency{ID = Guid.NewGuid()};

      context1.Currencies.Add(c);

      c.Countries = new List<Country>();

      c.Countries.Add(new Country{ID = Guid.NewGuid()});

      context1.SaveChanges();
   }

   using (MyContext context2 = new MyContext()){
     Currency c = context2.Currencies.FirstOrDefault();

     context2.Currencies.Remove(c);

     //throws exception due to foreign key constraint
     //The primary key value cannot be deleted 
     //because references to this key still exist.   
     //[ Foreign key constraint name = Country_Currency ]

     context2.SaveChanges();
    }          
  }
}

Вы получите сообщение об ошибке при сохранении, потому что вы удаляете что-то, что является обязательным внешним ключом.

person Xhalent    schedule 23.03.2011
comment
Спасибо за ответ, Xhalent. К сожалению, я, вероятно, недостаточно ясно выразился: в моей модели каждой стране НУЖНА валюта. Итак, мне действительно нужно, чтобы валюта была [Обязательно]. Другими словами: я полностью доволен тем, что не могу удалить валюту, если для нее назначена хотя бы одна страна. Однако это не работает должным образом. Каскадное удаление все равно удалит страну, если я удалю соответствующую валюту. И это НЕ хорошо, и я не могу объяснить это себе, так как я думал, что отключил CASCADE DELETE с помощью своего свободного оператора API. - person Ingmar; 23.03.2011
comment
в этом случае оставьте [Required] и измените modelBuilder, чтобы использовать .HasRequired вместо .HasOptional, и вы не сможете удалить валюту, в которой есть страны - person Xhalent; 23.03.2011
comment
@Xhalent: мне следует использовать .HasRequired вместо .HasOptional в моем исходном операторе свободного API? Уверены ли вы? Теперь я в полном замешательстве. Разве это не означает, что каждая валюта ДОЛЖНА иметь страну? Если это так, это не будет соответствовать моему бизнес-правилу, которое гласит, что валюта МОЖЕТ иметь назначенную страну (ноль, один, неограниченно). - person Ingmar; 23.03.2011
comment
Нет, я имею в виду использование MY Fluent modelbuilder, но вместо HasOptional используйте .HasRequired. Это означает, что у каждой страны должна быть валюта, но у валюты МОЖЕТ быть много стран (или ни одной). - person Xhalent; 24.03.2011
comment
@Xhalent: modelBuilder.Entity‹Country›() .HasRequired(cou =› cou.Currency) .WithMany() .WillCascadeOnDelete(false); - person Ingmar; 24.03.2011
comment
@Xhalent: Это заявление FluentAPI, о котором вы думали? Но это более или менее именно тот FluentAPI, который я использую в своем первом посте. Кроме того, даже когда я использую вашу, все тот же эффект: удаление валютного каскада удаляет прикрепленные страны. И этого просто НЕ должно происходить в отношении оператора .WillCascadeOnDelete(false). Вот я и думаю, это баг или я что-то упускаю... - person Ingmar; 24.03.2011
comment
Fluent API в вашем исходном сообщении не имеет спецификации WithMany, которую я определил .HasMany(c=›c.Countries), поэтому не вижу, насколько они более или менее одинаковы. Когда я тестировал это, он определенно прекратил каскадное удаление. В приведенном выше коде у вас есть пустая спецификация HasMany(), которая ничего не значит - вам нужно указать, какое отношение вы не хотите каскадировать. - person Xhalent; 24.03.2011