Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add a new entity with relationships to existing entities without getting duplicates? (EF 6.1)

Using Web API 2 and EF 6.1 code first.

I am trying to add a new Template (see model) which has relationships to already existing TimePeriods and Stations.

public class Template
{
    public int TemplateID { get; set; }
    public string Name { get; set; }

    public List<TimePeriod> TimePeriods { get; set; }
    public List<Station> Stations { get; set; }
}

public class Station
{
    public int StationID { get; set; }
    public string Name { get; set; }

    public List<Template> Templates { get; set; }
}

public class TimePeriod
{
    public int TimePeriodID { get; set; }
    public TimeSpan From { get; set; }
    public TimeSpan To { get; set; }
    public List<Template> Templates { get; set; }
}

The new template object contains a list of Station and a list of TimePeriod with correct IDs/primary keys. I hoped that EF would recognize that the related entities were already existing by looking att their primary keys but it seems not. Instead, all the related entities are added again resulting in duplicates.

private SchedulingContext db = new SchedulingContext();

[ResponseType(typeof(Template))]
public IHttpActionResult PostTemplate(Template template)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    db.Templates.Add(template);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new { id = template.TemplateID }, template);
}

Does this have something to do with me using a new context? If so, how can I prevent this behavior?

Solution thanks to Evandro:

public void PostTemplate(Template template)
{
    db.Templates.Add(template);

    foreach (var item in template.Stations)
    {
        db.Entry<Station>(item).State = EntityState.Unchanged;
    }

    foreach (var item in template.TimePeriods)
    {
        db.Entry<TimePeriod>(item).State = EntityState.Unchanged;
    }
    db.SaveChanges();
}
like image 818
Lorentz Lasson Avatar asked Nov 01 '22 00:11

Lorentz Lasson


1 Answers

Lorentz, this is the default behavior of the Entity Framework. You have to explicitly define your custom behavior based on what the system should do.

First, you can access the State of your entities within the context using hte following example:

   EntityState state = db.Entry<Station>(station).State;

You can print the states and then see what EF is doing.

Now, when you first receive the instance of Template, its state on the context it will be Detached.

After you add it to the context, the state will change to Added. This will apply for Template(s), Station(s) and TimePeriod(s).

Even if you set the Id (Primary Key) correctly, EF will discard the ids, create new Ids and add new lines to the tables, which is what is happening with your program. That is what I managed to reproduce in my code.

You have to define the EntityState for each entity so the EF will know that it should not persist new items. Below are the possible values at EF 6.1:

// This is probably what you are looking for
db.Entry<Station>(station).State = EntityState.Unchanged;

// This one maybe, if you are receiving updated values for the State
db.Entry<Station>(station).State = EntityState.Modified; 

// Others that may apply for other scenarios
db.Entry<Station>(station).State = EntityState.Detached;
db.Entry<Station>(station).State = EntityState.Added;
db.Entry<Station>(station).State = EntityState.Deleted;

Since Template have multiple itens for Station and TimePeriod you will have to iterate over them and set each one as "Unchanged" I assume, or "Modified".

Let me know if it works.

like image 90
Evandro Pomatti Avatar answered Nov 15 '22 04:11

Evandro Pomatti