locked
Remove() with many-to-many relationship doesn't work in Entity Framework 4.1 RRS feed

  • Question

  • Hi All, I am trying to remove an object from a collection in entity framework, but unfortunately my code is failing. I would be grateful if you could have a look and let me know if you can figure out what I'm doing wrong. My objects are as follows:

    • Person <-> Badge (many-to-many relationship)
    • Badge <-> BadgeRequirement (one-to-many relationship)
    • Person contains an ICollection of Badges
    • Badge contains an ICollection of Person
    • BadgeRequirement contains a Badge Foreign Key

    Adding and editing entries works absolutely fine.

    However, when I try to remove a Badge from a Person using the code below, it doesn't work:

    Postback event handler on example.aspx
    ****The person object has been loaded as part of the load event on the page****
    
    Badge badge = BadgeHelper.getBadge(badgeID);
    if (command == "Delete")
    {
     PersonHelper.removeBadgeFromPerson(badge, person);
     }
    
     Delete method on PersonHelper class (wrapper for all processing)
    
     person.Badges.Remove(badge);
     DbContext.SaveChanges();
    

    The Remove(badge) returns false and I cannot profile this as I am using SQL Compact 4.0.

    Could this be a cascade delete due to the BadgeID Foreign Key in BadgeRequirement which I'm missing?

    Thanks in advance for your help!

    Sunday, April 17, 2011 9:36 AM

Answers

  •  

    Hi cmat1978,

    Welcome!

    According to your description, I think you should explicitly loading the related entities and then call Remove method.

    I'd like to share the steps with you, you can debug your application you could watch "person.Badges", you will find it is null. You can add this before Remove metod: DbContext.Entry(person).Collection("Badges").Load();

    Here is my test code:

    class Program  {
        static void Main(string[] args)
        {
          using (var context= new MyContext())
          {
            var post1 = context.Posts.Find(3);
            var tag1 = context.Tags.Find(2);
            context.Entry(post1).Collection("Tags").Load();
            post1.Tags.Remove(tag1);        
            context.SaveChanges();
          }
        }
      }
      public class Post
      {
        public int PostId { get; set; }
        public string PostContext { get; set; }
        public ICollection<Tag> Tags { get; set; }
      }
      public class Tag
      {
        public int TagId { get; set; }
        public string TagContext { get; set; }
        public ICollection<Post> Posts { get; set; }
      }
      public class MyContext:DbContext
      {
        public DbSet<Post> Posts { get; set; }
        public DbSet<Tag> Tags { get; set; }
      }
    
    

    Please try it, if my reply couldn't meet your question, please feel free to let me know!

    Have a nice day.


    Alan Chen[MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, April 21, 2011 2:36 AM

All replies

  • Hi cmat1978,

    I would like to share something I learned from Diego:

    Cascade deletes is done in two cases. If you have cascade delete turned on on the association, then deleting the parent would delete the child. Regardless if the cascade is enabled on the database or not, EF will do the cascading at the object context to keep the object context in sync with the database.

    The second case where case delete happens is in identifying relationship. Its a relationship where a child cannot exists without parent. Well you must ask in what case a child cannot exist without a parent? When the child entity's identity which is the entity key includes the parents key. For example:

    For a principal Customer with primary key CustomerID and a dependent Order with primary key OrderID and separate foreign key CustomerID, the EF would not delete the Order entity if it was removed form the Customer.Orders collection. If however the Order had a primary key of {OrderID, CustomerID}, then EF would perform the delete. 

    An order entity may have an entity key comprising of orderId and customerid. Now when you remove the order from the customer's order collection, you have technically distored the order because it cannot be uniquely identify since its entity key has changed.

    Therefore an entity that cannot be uniquely identified has to be marked for deletion.

    Hope this can help.

     

    Thanks,

    Tony

    Monday, April 18, 2011 7:05 AM
  •  

    Hi cmat1978,

    Welcome!

    According to your description, I think you should explicitly loading the related entities and then call Remove method.

    I'd like to share the steps with you, you can debug your application you could watch "person.Badges", you will find it is null. You can add this before Remove metod: DbContext.Entry(person).Collection("Badges").Load();

    Here is my test code:

    class Program  {
        static void Main(string[] args)
        {
          using (var context= new MyContext())
          {
            var post1 = context.Posts.Find(3);
            var tag1 = context.Tags.Find(2);
            context.Entry(post1).Collection("Tags").Load();
            post1.Tags.Remove(tag1);        
            context.SaveChanges();
          }
        }
      }
      public class Post
      {
        public int PostId { get; set; }
        public string PostContext { get; set; }
        public ICollection<Tag> Tags { get; set; }
      }
      public class Tag
      {
        public int TagId { get; set; }
        public string TagContext { get; set; }
        public ICollection<Post> Posts { get; set; }
      }
      public class MyContext:DbContext
      {
        public DbSet<Post> Posts { get; set; }
        public DbSet<Tag> Tags { get; set; }
      }
    
    

    Please try it, if my reply couldn't meet your question, please feel free to let me know!

    Have a nice day.


    Alan Chen[MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, April 21, 2011 2:36 AM
  • Hi Alan,

    Your answer did it!! You are brilliant and this issue had my head spinning for many hours. The following lines are required for removing objects:

    var post1 = context.Posts.Find(3);
        var tag1 = context.Tags.Find(2);
        context.Entry(post1).Collection("Tags").Load();
        post1.Tags.Remove(tag1);    
        context.SaveChanges();
    
    

    However, i think that it is quite ugly that I have to do round trips to the database to load detached objects over and over again. In this scenario, I have to load both the post1 and the tag1 objects plus the full "Tags" Collection in order to do a remove.

    Is this one of the EF features that I need to learn to live with? Regardless of my comment, I have to say that I am grateful for helping out on this issue.

    I hope that this will help more people dealing with similar issues.

    Thanks again,

    Christos 

     

     

     

    Tuesday, April 26, 2011 9:57 AM
  • I have Similar Issue .. m using Silverlight  and not able to find context.Entry() Function. My Actual Scenario is as below:

    I am not able to remove record from table which has many-to-many relationship. For many-to-many relationship I use M2M4Ria...

    Store => StoreID, StoreName

    PaymentType => PaymentTypeID , PaymentType and

    StorePaymentType => StoreID, PaymentTypeID

    When I try to remove StorePaymentType object like this:

    storeObject.StorePaymentTypeToPaymentTypeSet.Remove(storePaymentTypeObject);

    it doesn't give any error.. but at the same time it doesn't reflect in the database. When I checked the EntityState of storeObject it states Unmodified.

    Can Anyone please help me out solving this issue.

    Thank you very much for your time and help.

     

    Monday, January 30, 2012 7:30 AM
  • Hi ertejaspatel,

    context.Entry() function is for EF4.1 code first, for EF4.0 you can refer this link: http://msdn.microsoft.com/en-us/library/bb896272.aspx

    var storeObject= context.Stores.FirstOrDefault();
    store.StorePaymentTypeToPaymentTypeSet.Load();
    storeObject.StorePaymentTypeToPaymentTypeSet.Remove(storePaymentTypeObject);
    
    Have a nice day.

     


    Alan Chen[MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, January 30, 2012 7:57 AM
  • Hi alan,

    I tried but unfortunately I am not able to find even Load Method. I checked my Lazy Loading is set to true. Do I need to explicitly set anything else to get Load method.

    Monday, January 30, 2012 8:34 AM
  • Hi,

    >>var storeObject= context.Stores.FirstOrDefault();

     You can debug to watch if storeObject.StorePaymentTypeToPaymentTypeSet.Count is 0.

    Please try to add Include("navigationname")

    var storeObject= context.Store.Include("").FirstOrDefault();

    Have a nice day.

     


    Alan Chen[MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, January 30, 2012 8:41 AM
  • Thanks Alan.. But unfortunately this didn't work as well... i tried several ways to load data from table having Many To Many Relationship before removing data from it. But I don't know where am I going wrong. Is there any other way I can have Add and Remove Method for Many 2 Many Relationship table without using  M2M4Ria.

    Thanks for your help and time...

     

     

    Monday, January 30, 2012 9:52 AM
  • I think I understand now the nature of the problem (correct me if I'm wrong) and why the above solution is necessary. When an entity instance is loaded, its navigation-property collections are NOT automatically populated. If you try to delete a related item from a collection that has not been pre-populated--i.e., populated by using .Entry(item).Collection("NavigationProperty").Load above or by querying for the main item with .Include("NavigationProperty")--that's tantamount to removing an item from an empty collection--a statement that doesn't throw an exception but also doesn't do anything; hence, no change is tracked for SaveChanges.

    In fact, any time you want to access a navigation-property collection--to query an item, to add an item, etc.--you must load it explicitly first. Otherwise, you could have scenarios where items aren't found or are added to the collection multiple times. (I got this when I tried adding a related-entity instance that was already in the database but not already tied to the main entity, or when I tried using methods like Contains on the collection.) You must always precede a statement which manipulates the collection with something like Entry/Collection/Load or Include so that the related-entity instances in the collection are actually available. (BTW, if you're still using ObjectContext, you should try querying for the main item with .Include("NavigationProperty") prior to the Remove.)

    I think this anomaly can exist in 1-to-many scenarios as well as many-to-many. The difference is that, in a 1-to-many case, you can manipulate the relationship from the other entity which doesn't have a collection, or use foreign-key properties instead of navigation properties. These options don't exist when the relationship is many-to-many--in which both entities use collections and foreign-key properties aren't allowed.


    Robert Gustafson

    PS I don't know why this quirk isn't well-documented in EF books, but it damn well should be!







    Sunday, July 3, 2016 11:25 PM