sterix24
sterix24

Reputation: 2400

Updating records in a many to many relationship in Entity Framework

In my database I have two tables: Trucks and TruckTypes. Many trucks can have many truck types. I've created my models using the database designer and the intermediate table (that only holds primary keys) has been ignored, which I understand is EF's default behaviour:

enter image description here

When I check the models, it seems both now have an ICollection of the corresponding model which seems to be working. The problem comes when I try to add new items or update existing items.

At the moment I update my records with this:

using (var db = new Entities())
{
    var truckResult = from t in db.ant_Truck
                        where t.Id == model.Id
                        select t;

    var truck = truckResult.Single();

    truck.Name = model.Name;
    truck.ant_TruckType.Clear();
    foreach (var truckType in model.TruckTypes)
        truck.ant_TruckType.Add(new ant_TruckType()
        {
            Name = truckType.Name,
            Disabled = truckType.Disabled
        });
    truck.Disabled = model.Disabled;

    db.SaveChanges();
}

I think I understand the problem: That the new ant_truckType models are being added as 'new' rather than linking to existing records in the 'ant_truckType' table but I'm still not sure how to link them.

I tried creating another method that queried the table and attempted to return an ICollection<ant_truckType> but this starts giving me other errors about converting to an ICollection:

public ICollection<ant_TruckType> GetDbTruckTypesForTruck(ant_Truck truck)
{
    using (var db = new Entities())
    {
        var result = from tt in db.ant_TruckType
                        where tt.ant_Truck.Contains(truck)
                        select tt;

        var truckTypes = result.ToList() as ICollection<ant_TruckType>;

        return truckTypes;
    }
}

I'm not sure if I'm doing things correctly at this point as I've spent so long trying to understand why this isn't working. What's the most efficient way of trying to do the above?

Thanks in advance.

Upvotes: 0

Views: 31

Answers (1)

Steve Py
Steve Py

Reputation: 34773

Truck Types is a reference list, where a truck can have many Truck Types. I'd probably avoid adding a Trucks collection inside Truck Types just to simplify referencing. It's still many-to-many, but instead of:

.HasMany(x => x.TruckTypes)
.WithMany(x => x.Trucks)

... with the associated linking table config, it would be simplified to:

.HasMany(x => x.TruckTypes)
.WithMany()

When updating TruckTypes on a truck, you'll want a set of all TruckTypes to reference then you can split it up to determine which Types need to be added vs. removed:

Ideally your model just needs to send a list of Truck Type IDs that should be associated to the Truck. If you're sending Truck Types then add a step to get the relevant IDs you want to leave associated to the Truck.

using (var db = new Entities())
{
    // Load our truck and eager load its TruckTypes.
    var truck = db.ant_Truck.Include(t => t.TruckTypes).Single(t => t.Id == model.Id);
    
    // TODO: Update truck fields ...
    
    // Load all truck types we will want to associate with this truck.
    // For smaller sets we can simply load add truck types.
    var truckTypes = db.ant_TruckType.Where(tt => model.TruckTypeIds.Contains(tt.Id)).ToList();
    
    var existingTruckTypeIds = truck.TruckTypes.Select(tt => tt.Id).ToList();
    var truckTypeIdsToRemove = existingTruckTypeIds.Except(model.TruckTypeIds);
    var truckTypeIdsToAdd = model.TruckTypeIds.Except(existingTruckTypeIds);

    var truckTypesToRemove = truck.TuckTypes.Where(tt => truckTypeIdsToRemove.Contains(tt.Id));
    var truckTypesToAdd = truckTypes.Where(tt => truckTypeIdsToAdd.Contains(tt.Id));

    foreach(var truckType in truckTypesToRemove)
        truck.TruckTypes.Remove(truckType);
    foreach(var truckType in truckTypesToAdd)
        truck.TruckTypes.Add(truckType);

    db.SaveChanges();
}

We load the truck and include its current truck types, then load references to all truck types we want associated with the truck from the DB Context. From there we take a simple inventory of the truck type IDs on our truck model. From that we can use Except to determine the truck type IDs that need to be removed from our truck and added to the truck. Those resulting ID lists can then get references from our truck's types (to remove) and from the loaded truck types (to add).

With bidirectional references you may need the overhead of going through the Trucks collection on the respective TruckType and removing or adding the current Truck from it as well. When it comes to bi-directional references, I recommend avoiding them unless they are truly necessary/helpful. You can always filter truck types by truck by querying criteria against Truck then using SelectMany to provide the TruckTypes.

Upvotes: 1

Related Questions