简体   繁体   中英

Entity Framework Saving Many-to-Many Relationship with Codefirst, Generic Repository, Unit of Work Pattern

I'm having a problem getting many-to-many relationships to save correctly using EF Codefirst. I've modeled my classes correctly and used Fluent-API to correctly model the join table. I think the issue has to do with working with a disconnected DTO. When I save changes to the parent entity (Condo), scalular properties on the parent entity (such as Title, and UserId) save correctly, but changes to a child entity (Amenities) do not save to the many to many table.

Here's the code flow that will help clarify things:

public ICommandResult Execute(CreateOrUpdateCondoCommand command)
    {
        ICollection<Amenity> amenities = new List<Amenity>();

        foreach (var item in command.Amenities)
        {
            Amenity amenity = new Amenity { AmenityId = item.AmenityId };
            amenities.Add(amenity);
        }

        var condo = new Condo
        {
            [...other properties]
            Title = command.Title,               
            Amenities = amenities             
        };                  

        if (condo.CondoId == 0)
        {
            condoRepository.Add(condo);
        }
        else
        {
            condoRepository.Update(condo);
        }

        unitOfWork.Commit();

        return new CommandResult(true);
    }

    /// <summary>
    /// Updates the entity.
    /// </summary>
    /// <param name="entity">The entity</param>
    public virtual void Update(T entity)
    {
        dbset.Attach(entity);
        dataContext.Entry(entity).State = EntityState.Modified;
    }

I was able to get things working by creating a condoRepository.UpdateCondo(condo) method as follows:

/// <summary>
    /// Method for updating a condo
    /// </summary>   
    /// <param name="condo">The condo to update</param>
    public void UpdateCondo(Condo condo)
    { 
        var updatedCondo = this.DataContext.Set<Condo>().Include("Amenities")
            .Single(x => x.CondoId == condo.CondoId);

        // Set the attributes
        [other properties here...]
        updatedCondo.Title = condo.Title;           

        updatedCondo.Amenities.Clear();

        foreach (var amenity in condo.Amenities)
        {
            var amenityToAttach = this.DataContext.Amenities.Single(x => x.AmenityId == amenity.AmenityId);
            updatedCondo.Amenities.Add(amenityToAttach);                
        }

        this.Update(updatedCondo);
    }

However, is there a better way to do this that is generic and doesn't require me to create a custom "Update" method every time I need to save a many-to-many relationship? This https://stackoverflow.com/a/11169307/3221076 answer helped clarify what I think the problem is, but I'm not sure how to implement a more generic approach.

Thanks, Jason

I don't know about making it completely generic, but I have done something like this, which allows you to add, remove, and modify master-detail relationships. (I think the original idea came from Steven Sanderson, but I could not find it.) This code is in my DbContext. Hope it helps.

public Condo UpdateSheet(Condo condo) {
    Guard.IsNotNull(condo);

    List<long> retainedAmenityIds = (from amenity in condo.Amenities
                                      where amenity.Id != 0
                                      select amenity.Id).ToList();
    List<Amenity> retainedAmenities = (from amenity in condo.Amenities
                                        where amenity.Id != 0
                                        select amenity).ToList();
    string sql = String.Format("SELECT Id FROM {0} WHERE CondoId = {1}", GetTableName<Amenity>(), condo.Id);
    List<long> deletedAmenityIds = Database.SqlQuery<long>(sql).Except(retainedAmenityIds).ToList();
    if (0 < deletedAmenityIds.Count) {
        foreach (var id in deletedAmenityIds) {
            Amenity amenity = Amenities.Single(a => a.Id == id);
            Amenities.Remove(amenity);
        }
    }
    List<Amenity> addedAmenities = condo.Amenities.Except(retainedAmenities).ToList();
    foreach (var amenity in addedAmenities) {
        amenity.SheetId = condo.Id;
        Entry(amenity).State = EntityState.Added;
    }
    foreach (var amenity in retainedAmenities) { Entry(amenity).State = EntityState.Modified; }
    Entry(condo).State = EntityState.Modified;
    SaveChanges();
    return condo;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM