4

I have to admit, the features of EF 4.1 RC Codefirst, DataAnnotations and FluentAPI are still overwhelming to me. Sometimes I really don't know what I am doing ;-) Please see the following POCOs:

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; }
}

The general idea: Every country needs to have a currency. But a currency does not need to be assigned to a country at all.

If you let EF create the corresponding database, the relationship will be set to CASCADE DELETE by convention. In other words: if you delete a currency, the corresponding countries are deleted as well. But in my case this is not what I want.

I came up with some code in FluentAPI in order to disable CASCADE DELETE:

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

I thought this means: Every country requires a currency. And this currency might have zero, one or more countries assigned (optional). And whenever I delete a currency, the corresponding countries (if there are any) will NOT be cascade deleted.

Surprisingly the given approach will still cascade delete a country if I delete the corresponding currency. Can anybody tell me what I miss?

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
Ingmar
  • 571
  • 2
  • 6
  • 13

1 Answers1

5

Firstly you've specified the currency as a required field on country, so you can't delete a currency. You'll need to remove the [Required].

Secondly, your model builder need the following:

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

Thirdly, you need to explicitly remove the reference to the entity you are deleting from it's children:

 Currency c = context.Currencies.FirstOrDefault();

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

                context.Currencies.Remove(c);

                context.SaveChanges();

[EDIT] Because I suspect there is something lost in translation find the complete code that demonstrates how no-cascading deletes would work.

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();
    }          
  }
}

You will get an error on saving, because your deleting something that is a required foreign key.

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
Xhalent
  • 3,914
  • 22
  • 21
  • Thanks for your reply, Xhalent. Unfortunately I probably didn't make myself clear enough: In my model every country NEEDS a currency. So, I really need the currency to be [Required]. In other words: I am totally happy not being able to delete a currency if there is at least one country assigned to. However, this is not working as expected. Cascade delete will still delete a country if I delete the corresponding currency. And this is NOT good, and I can't explain it it to myself since I thought I turned CASCADE DELETE off by my fluent API statement. – Ingmar Mar 23 '11 at 09:44
  • in that case, leave the [Required], and modify the modelBuilder to use .HasRequired instead of .Hasoptional and you wont be able to delete a currency that has countries – Xhalent Mar 23 '11 at 10:19
  • @Xhalent: I should use .HasRequired instead of .HasOptional in my original fluent API statement? Are you sure? Now I am totally confused. Wouldn't that mean that every currency MUST have a country? If so, that would not match my business rule which says that a currency CAN have a country assigned (zero, one, unlimited). – Ingmar Mar 23 '11 at 17:29
  • No, I mean use MY Fluent modelbuilder but instead of HasOptional use .HasRequired. That mean each country must have a currency, but a currency MAY have many countries (or none). – Xhalent Mar 24 '11 at 10:56
  • @Xhalent: modelBuilder.Entity() .HasRequired(cou => cou.Currency) .WithMany() .WillCascadeOnDelete(false); – Ingmar Mar 24 '11 at 15:26
  • @Xhalent: Is that the FluentAPI statement you were thinking of? But this is more or less exactly the FluentAPI that I am using in my first post. Besides, even when I use yours, still the same effect: Deleting a currency cascade deletes the attached countries. And this just should NOT happen concerning the .WillCascadeOnDelete(false) operator. So, I wonder if this is a bug or if I am missing something... – Ingmar Mar 24 '11 at 15:28
  • 1
    The fluent API in your original post does not have the WithMany specification that I have defined .HasMany(c=>c.Countries), so can't see how they are "more or less exactly the same". When I tested this it certainly stopped cascading deletes. In your code above,you have an empty HasMany() specification which doesn't mean anything- you need to say which relatinship you don;t want to cascade. – Xhalent Mar 24 '11 at 20:28