3

I'm working with an entity framework project where I'm struggling to remove an Item out of one of my collections. I have firstly a "One to Many" relation created between my object "Resto" and "OpeningTimes" the following way:

In my model:

 [Table("Resto")]
public class Resto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string PhoneNumber { get; set; }
    public string Address { get; set; }   //TODO Ajouter adresse détaillées 

    public virtual ICollection<SlotTime> OpeningTimes {get; set;}




    public virtual ICollection<ApplicationUser> Administrators { get; set; }
    public virtual ICollection<ApplicationUser> Chefs { get; set; }

    public virtual Menu Menu {get; set;}
}
[Table("RestoSlotTimes")]

public class SlotTime
{
    public int SlotTimeId { get; set; }
    public DayOfWeek DayOfWeek { get; set; }
    public TimeSpan OpenTime { get; set; }
    public TimeSpan CloseTime { get; set; }


    public int RestoId { get; set; }
    public virtual Resto Resto { get; set; }
}

Since I have other relation from that object (see the one from Applications User) I'm also using Fluent language to remove ambiguities.

The following way:

        modelBuilder.Entity<Resto>()
                    .HasMany<ApplicationUser>(s => s.Administrators)
                    .WithMany(c => c.Resto_Admin)
                    .Map(cs =>
                    {
                        cs.MapLeftKey("Resto_Admin");
                        cs.MapRightKey("Admin");
                        cs.ToTable("RestosAdmins");
                    });

        modelBuilder.Entity<Resto>()
        .HasMany<ApplicationUser>(s => s.Chefs)
        .WithMany(c => c.Resto_Chefs)
        .Map(cs =>
        {
            cs.MapLeftKey("Resto_Chefs");
            cs.MapRightKey("Chef");
            cs.ToTable("RestosChefs");
        });
modelBuilder.Entity<Resto>()
                    .HasOptional(s => s.Menu) 
                    .WithRequired(ad => ad.resto)
                    .WillCascadeOnDelete(true); 

But those relations are working fine.

Today I do basic operations on my "OpeningTimes" in my controller as adding a item in the DB with the following:

        [HttpPost]
    public async Task<ActionResult> AddSlotTimeToRestaurant(AddSlotTimeToRestaurantView model)
    {
        var resto = await DbManager.Restos.FirstAsync(r => r.Id == model.RestoId);
        if(resto != null)
        {
            if(model.OpenTimeId < model.CloseTimeId)
            {
                SlotTime slotTime = new SlotTime()
                {
                    DayOfWeek = model.Day,
                    OpenTime = model.SlotTimeList.timeSpanViews.FirstOrDefault(m => m.Id == model.OpenTimeId).TimeSpan,
                    CloseTime = model.SlotTimeList.timeSpanViews.FirstOrDefault(m => m.Id == model.CloseTimeId).TimeSpan
                };
                resto.OpeningTimes.Add(slotTime);
                await DbManager.SaveChangesAsync();
                return RedirectToAction("edit", new { id = model.RestoId });
            }
            else
            {
                ModelState.AddModelError("SelectedSlotTimeId_1_Stop", "L'heure de fermeture doit être après l'heure d'ouverture");
                return View();
            }               
        }
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

This code is working as expected. But now one I try to do another function for removing the object with he following code:

   public async Task<ActionResult> RemoveSlotTimeToRestaurant(int RestoId, int SlotId)
    {
        var resto = await DbManager.Restos.FirstAsync(r => r.Id == RestoId);
        if (resto != null)
        {
            SlotTime slotTime = resto.OpeningTimes.FirstOrDefault(m => m.SlotTimeId == SlotId);

            if(slotTime != null)
            {
                resto.OpeningTimes.Remove(slotTime);
                await DbManager.SaveChangesAsync();
                return RedirectToAction("edit", new { id = RestoId });
            }
        }
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

I have an error on the DbSave line... Where it looks like EF try not to remove my object but just to keep it and set its ID to Null... Which is not what I expect

The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted

I have the feeling my relation is well configured for EF. I feel like doing the add and removing relation properly... Do you think this can come from the fluent configuration?

BTW once I remove the "Resto" I have the cascade deleting working well where all my items "OpeningTimes" are well deleted out of the DB without error.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38

4 Answers4

4

If you want to be able to modify the child entity (OpeningTimes) through the parent entity (Resto), then you need to specify the relationship between the two in the model builder:

modelBuilder.Entity<Resto>()
    .HasMany(r => r.OpeningTimes) 
    .WithOptional()
    .HasForeignKey(ot => ot.RestoId)

This blog post goes into more detail on handling this scenario.


Another approach would be to just remove the entity directly from the DbSet defined on the DbContext:

DbManager.SlotTimes.Remove(slotTime);
devNull
  • 3,849
  • 1
  • 16
  • 16
  • Hello devNull. I tried as you proposed the fluent approach but the issue is still the same... Same error message. – Jonathan Halleux Jan 07 '19 at 20:12
  • And for the second approach, at this I did not specified slotTime in the DbSet. I only access it through Resto from his Childs... – Jonathan Halleux Jan 07 '19 at 20:13
  • Yes finally your second approach sounds to be the right one. Found finally this here :https://weblogs.asp.net/zeeshanhirani/removing-entity-from-a-related-collection – Jonathan Halleux Jan 07 '19 at 20:25
  • BTW as I sayd above "OpeningTimes" is not available through my DbManager... I belive because noe set in the DBSet... Should I declare it there? Is it the best way to declare the Childs in the DbSet? – Jonathan Halleux Jan 07 '19 at 20:34
1

Can you try like below:

DbManager.Entry(slotTime).State = EntityState.Deleted;
await DbManager.SaveChangesAsync();
sri harsha
  • 676
  • 6
  • 16
  • 1
    Hello,Yes this way it is working... Sounds like EF does not understand "Remove" as "delete" action ;-) – Jonathan Halleux Jan 07 '19 at 16:13
  • 1
    EF will understand remove as removing that row and it will not consider the cascade rule, so it will make as null in parent table by removing the child table record. If cascade rule is properly set and if you want to remove the cascade rule based means, you need to give the above statements. This applies to one-to-many relationship scenarios. – sri harsha Jan 07 '19 at 16:22
1

It looks to me like you need to add another fluent call, to define how the SlotTimes relate to the Resto.

From what you're describing, the SlotTime is entirely dependent on the Resto: you can't have a SlotTime that isn't associated with a Resto. But, evidently EF can't just infer this: you're going to have to tell it, with fluent configuration.

I can't recreate this from memory because it's too complicated, and I'm not on a machine with VS installed. But I think you'd start with this:

  modelBuilder.Entity<Resto>()
    .HasMany<TimeSlot>(r => r.OpeningTimes)
    .WithOne(t => t.Resto)
    ....

I apologize for not being able to fill out the whole call for you.

By the way, this other question has a very good answer that explains it all: EF 4: Removing child object from collection does not delete it - why?

Ann L.
  • 13,760
  • 5
  • 35
  • 66
  • Thank Ann, Indeed your approach will not work (like the one devNull). But could find the answer further in the link you send. There is the full explainiation vailable here https://weblogs.asp.net/zeeshanhirani/removing-entity-from-a-related-collection – Jonathan Halleux Jan 07 '19 at 20:23
  • 1
    @JonathanHalleux Glad you found the answer! – Ann L. Jan 08 '19 at 13:32
0

When you remove the slotTime, is there another table that has a foreign key related to the OpeningTimes table? It looks as if you are removing the item, and the related table is trying to set the foreign key to null in which that is not allowed. You need to make sure tables related to OpeningTimes are changed appropriately to account for the removal of the record.

Edit: Other answers are more detailed and they're much better than mine.

Going-gone
  • 282
  • 1
  • 14