Entity Framework, Code First, Update “one to many” relationship with independent associations

前端 未结 2 2047
走了就别回头了
走了就别回头了 2021-02-07 15:41

It took me way too long to find a solution to the scenario described below. What should seemingly be a simple affair proved to be rather difficult. The question is:

Usi

相关标签:
2条回答
  • 2021-02-07 16:20

    There is a lot of information to be found on this topic; on stackoverflow I found Ladislav Mrnka's insights particularly helpful. More on the subject can also be found here: NTier Improvements for Entity Framework and here What's new in Entity Framework 4?

    In my project (Asp.Net Webforms) the user has the option to replace the Customer assigned to a Target object with a different (existing) Customer object. This transaction is performed by a FormView control bound to an ObjectDataSource. The ObjectDataSource communicates with the BusinessLogic layer of the project which in turns passes the transaction to a repository class for the Target object in the DataAccess layer. The Update method for the Target object in the repository class looks like this:

        public void UpdateTarget(Target target, Target origTarget)
        {
            try
            {
                // It is not possible to handle updating one to many relationships (i.e. assign a 
                // different Customer to a Target) with "Independent Associations" in Code First.
                // (It is possible when using "ForeignKey Associations" instead of "Independent 
                // Associations" but this brings about a different set of problems.)
                // In order to update one to many relationships formed by "Independent Associations"
                // it is necessary to resort to using the ObjectContext class (derived from an 
                // instance of DbContext) and 'manually' update the relationship between Target and Customer. 
    
                // Get ObjectContext from DbContext - ((IObjectContextAdapter)tgrDbContext).ObjectContext;
                ObjectContext tgrObjectContext = TgrDbContext.TgrObjectContext(_tgrDbContext);
    
                // Attach the original origTarget and update it with the current values contained in target
                // This does NOT update changes that occurred in an "Independent Association"; if target
                // has a different Customer assigned than origTarget this will go unrecognized
                tgrObjectContext.AttachTo("Targets", origTarget);
                tgrObjectContext.ApplyCurrentValues("Targets", target);
    
                // This will take care of changes in an "Independent Association". A Customer has many
                // Targets but any Target has exactly one Customer. Therefore the order of the two
                // ChangeRelationshipState statements is important: Delete has to occur first, otherwise
                // Target would have temporarily two Customers assigned.
                tgrObjectContext.ObjectStateManager.ChangeRelationshipState(
                    origTarget,
                    origTarget.Customer,
                    o => o.Customer,
                    EntityState.Deleted);
    
                tgrObjectContext.ObjectStateManager.ChangeRelationshipState(
                    origTarget,
                    target.Customer,
                    o => o.Customer,
                    EntityState.Added);
    
                // Commit
                tgrObjectContext.Refresh(RefreshMode.ClientWins, origTarget);
                tgrObjectContext.SaveChanges();
            }
            catch (Exception)
            {
                throw;
            }
        }            
    

    This works for the Update method for the Target object. Remarkably, the procedure for inserting a new Target object is way easier. DbContext recognizes the Customer end of the independent association properly and commits the change to the database without further ado. The Insert method in the repository class looks like this:

            public void InsertTarget(Target target)
        {
            try
            {
                _tgrDbContext.Targets.Add(target);
                _tgrDbContext.SaveChanges();
            }
            catch (Exception)
            {
                throw;
            }
        }
    

    Hopefully this will be useful to somebody dealing with a similar task. If you notice a problem with this approach described above, please let me know in your comments. Thanks!

    0 讨论(0)
  • 2021-02-07 16:36

    I waited for @Martin answer because there are more solutions for this problem. Here is another one (at least it works with ObjectContext API so it should work with DbContext API as well):

    // Existing customer
    var customer = new Customer() { Id = customerId };
    // Another existing customer
    var customer2 = new Customer() { Id = customerId2 };
    
    var target = new Target { ID = oldTargetId };
    // Make connection between target and old customer
    target.Customer = customer;
    
    // Attach target with old customer
    context.Targets.Attach(target);
    // Attach second customer
    context.Customers.Attach(customer2);
    // Set customer to a new value on attached object (it will delete old relation and add new one)
    target.Customer = customer2;
    
    // Change target's state to Modified
    context.Entry(target).State = EntityState.Modified;
    context.SaveChanges();
    

    The problem here is internal state model and state validations inside EF. Entity in unchanged or modified state with mandatory relation (on many side) cannot have independent association in added state when there is no other in deleted state. Modified state for association is not allowed at all.

    0 讨论(0)
提交回复
热议问题