8

I want to implement a unidirectional one to one relationship; however on cascade delete doesn't work.

I have the following classes:

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public string Street { get; set; }
    //I don't want the StudentId foreign key or the property of Student class here 
}

In my Context class, I'm mapping the relationship like this:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Student>()
        .HasRequired(s => s.Address)
        .WithOptional()
        .Map(m => m.MapKey("Address_Id"))
        .WillCascadeOnDelete();
}

For some reason, it's not deleting the address when, student object is deleted.

Moreover, I also want to add the foreign key property (i.e. AddressId) in the Student class like this:

[ForeignKey("Address")]
[Column("Address_Id")]
public string AddressId { get; set; }

However, I get this error when I try to add a new migration:

Address_Id: Name: Each property name in a type must be unique. Property name 'Address_Id' is already defined.

I do believe I'm mixing things up (with MapKey and the attributes of AddressId). However, I don't know how to fix this.


I went through this SO question and this article; however, no luck so far.

Link to DotNetFiddle. It won't work cause there is no database.

Community
  • 1
  • 1
Dumbledore
  • 450
  • 1
  • 5
  • 20

2 Answers2

3

You foreign key should be like this :

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    [ForeignKey("AddressId")]
    public Address Address { get; set; }        
    [Column("Address_Id")]
    public int AddressId { get; set; }
}

In your fluent mapping you just need:

modelBuilder.Entity<Student>()
    .HasRequired(s => s.Address)
    .WillCascadeOnDelete(true);

Or you can force cascade delete with an annotation:

[Required]
[ForeignKey("AddressId")]
public Address Address { get; set; }

Now update your database and your mapping should be correct and the delete should cascade.

James Dev
  • 2,979
  • 1
  • 11
  • 16
  • Thanks for your answer. I'm sorry; however, the fluent mapping doesn't seem to work. `Required` annotation does work. However, EF doesn't load the `Address` object unless the `virtual` keyword is added to the property. Is it supposed to behave like that? Does the `virtual` keyword helps in lazy loading only when an `ICollection` is supposed to be retrieved? – Dumbledore Mar 30 '16 at 17:21
  • I'm sorry, I just looked into my database. Cascade delete doesn't work either. – Dumbledore Mar 30 '16 at 17:23
  • 1
    Virtual keyword lazy loads the related entity so the entity gets loaded when the parent object is loaded. Eager loading is when you add Include() onto the linq to entity query so there are 2 choices. – James Dev Mar 31 '16 at 08:27
  • Thanks James. Hopefully, I'm going to use the `Required` annotation. Currently, I can see `cascadeDelete` is set to `true` for the students table (in the migration file); however, `localdb` is acting funny for some reason. I have another question though. `Required` annotation would also make the foreign key non-nullable. Is there any way I can make foreign key `nullable`? – Dumbledore Mar 31 '16 at 13:14
  • You can make the foreign key nullable by making the property a nullable int? but that would not make the entity [Required] as this means the relationship is always required on the entity. But why would you want to do that? – James Dev Mar 31 '16 at 13:23
  • Well, now I'm using `[Required]` just cause I want to do cascade delete (fluent mapping doesn't work for some reason). So, my scenario here is "nullable foreign key with cascade delete". – Dumbledore Mar 31 '16 at 14:12
3

For some reason, it's not deleting the address when, student object is deleted.

That's the normal behavior for the relationship you have defined. It's not a matter of data annotations or fluent configuration. If you have different expectations, you'd better revisit your model.

Every relationship has a one side called principal and another side called dependent. The principal side (a.k.a. master, primary) is the one being referenced. The dependent side (a.k.a. detail, secondary) is the one that is referencing the principal. The foreign key is put on the dependent side and must always point to an existing principal or null when the relationship is optional. The cascade delete works by deleing all the dependent records when the principal record is deleted.

As explained in the How Code First Determines the Principal and Dependent Ends in an Association? section of the article mentioned by you, EF always uses the required side as principal and allows you to chose the one only when both are required.

With all that being said, let see what you have.

Address is required, Student is optional. Also you want to put FK in Student, i.e. Student references Address.

All that means that in your relationship, Address is the principal and Student is the dependent. Which means the Address may exists w/o Student referencing it. If cascade delete is turned on (as you did), deleting the Address will delete the Student, not the opposite.

I think all that should explain why it's is working the way it is now, and that no attributes or configuration can help to achieve what are you asking for. If you want it differently, the same article (and related from the same series) explains how to configure the relationship to either use Shared Primary Key Association or Foreign Key Association at the Address side. Whether it is unidirectional or bidirectional absolutely has nothing in common with the problem - see Should We Make the Associations Bidirectional? section in the article.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343