0

I currently have an object like this (simplified):

public class Image {
    public int Id { get; set; }

    public int ExternalId { get; set; }
}

Now let's say I have this method (mostly pseudo-code):

public void GetImage(int externalId) {
    var existingImage = db.Images.FirstOrDefault(i => i.ExternalId == externalId);

    if (existingImage != null) {
        return existingImage;
    }

    var newImage = new Image() { ExternalId = externalId };
    db.Images.Attach(newImage);
    db.SaveChanges();

    return newImage;
}

Because ExternalId isn't a key, the change tracker won't care if I have "duplicate" images in the tracker.

So now, let's say this method gets called twice, at the same time via AJAX and Web API (my current scenario). It's async, so there are two threads calling this method now.

If the time between calls is short enough (in my case it is), two rows will be added to the database with the same external ID because neither existing check will return a row. I've greatly simplified this example, since in my real one, there's a timing issue as I fetch the "image" from a service.

How can I prevent this? I need the image to be returned regardless if it's new or updated. I've added a Unique Constraint in the database, so I get an exception, but then on the client, the call fails whereas it should use the existing image instead of throwing an exception.

If I understand EF correctly, I could handle this by making ExternalId a primary key and then use concurrency to handle this, right? Is there any way to avoid changing my current model or is this the only option?

kamranicus
  • 4,207
  • 2
  • 39
  • 57

1 Answers1

1

If you already have property defining uniqueness of your entity (ExternalId) you should use it as a key instead of creating another dummy key which does not specify a real uniqueness of your entity. If you don't use ExternalId as a key you must put unique constraint on that column in the database and handle exception in your code to load existing Image from the database.

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • How would you suggest handling the error (and where), since the error message you get back is a regular `DbUpdateException` and I'd basically need to parse the error message to see what the underlying issue was to take action on. – kamranicus Dec 23 '12 at 23:26
  • I was able to solve the issue by handling the exception and specifically looking for a unique constraint violation. I will hopefully move to using ExternalId as the key, but in the meantime, this handles the situation. See this answer for how I handled it: http://stackoverflow.com/questions/3967140/duplicate-key-exception-from-entity-framework – kamranicus Dec 24 '12 at 01:30
  • Just an update. In another entity, I changed from using a single key to using a composite key to avoid this issue, but it still happened. It appears that even if I have two entities in an `Added` state and they both have the same entity key, EF will still add them both to the database, causing a constraint violation. Either you need to check for duplicates before hand or handle this exception. – kamranicus Dec 30 '12 at 20:45
  • You cannot have two entities with the same entity key in the same context. It should fire exception. If it does not there must be something else in your code. – Ladislav Mrnka Dec 30 '12 at 20:48
  • I think the exception occurs on SaveChanges because everything is committed at once. These are new entities that haven't yet had their entity key generated, so they are treated as separate objects. I wish I could explain better. I am looping through a bulk update model where some association entities are brand new. These are attached (context.Entities.Add(x)) but not saved until the end, where the `DbUpdateException` occurs. – kamranicus Dec 30 '12 at 20:54
  • Actually it can probably happen because EF internally uses temporary entity key before executing `SaveChanges` for new entities. – Ladislav Mrnka Dec 30 '12 at 21:01