3

On my edit page, I was receiving this error:

Attaching an entity of type failed because another entity of the same type already has the same primary key value.

So, I researched that and came to this.

That lead me to want to use Auto-Mapper, to simplify things.

Here is my code:

if (db.TableName.Find(value.ID).stringProperty.Equals(value.stringProperty, StringComparison.CurrentCultureIgnoreCase))
{
    Table inContextVariable = db.TableName.Find(value.ID);

    Mapper.Initialize(config => config.CreateMap<ModelName, ModelName>());

    Mapper.Map<ModelName, ModelName>(value, inContextVariable);

    db.Entry(inContextVariable).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction("Index");
}

This is leading me to this error:

Additional information: The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

This table has 1 Foreign Key, and when I debug that Foreign Key has a value of 11, so I am having a hard time understanding this error.


UPDATE


Imagine if I had a database table that held records containing a person's info such as:

|    ID    |    First Name    |    Last Name    |          Email           |
----------------------------------------------------------------------------
     1           John                Doe             john.doe@test.com
     2           Christopher       Columbus     |  chris.columbus@test.com |

And so on and so on.. now, since a person can only have 1 email address, you have to have safeguards against other users when they are creating/editing records..

For example, if I were to use this application and I wanted to create an account.. I shouldn't be able to successfully fill out the form with an email address that already exists in the database table..I should receive an error message saying something like

That email address already exists! Please enter another email address

Here is that code:

if (db.TableName.Any(x => x.Email.Equals(value.Email, StringComparison.CurrentCultureIgnoreCase)))
{
    ModelState.AddModelError("Email", "This email already exists!");
    return View(value);
}

Now.... what happens when a user successfully creates an account.. then goes to edit their account email address to something that already exists? The same error message should appear, which it does.

BUT when that user created their account, their email address is now in the database table.. so when they go to the Edit page, with their original email, and they hit 'Save', the code will check that email against all email addresses in the database, and it will see that it is already in the database causing the error message to show (please change your email. .etc)..

So I am in need of trying to create an efficient way of checking to see when the user is on the edit page, and hits 'Save' with their original email that it doesn't tell them to change their email.

So for that I have this code:

if (db.TableName.Find(value.ID).Email.Equals(value.Email, StringComparison.CurrentCultureIgnoreCase))
{
    var inContextVariable = db.TableName.Find(value.ID);
    MappingMethods.MapModelName(inContextVariable , value);

    \\ without the mapping.. I receive the error saying 'Attaching an entity of type failed because another entity of the same type already has the same primary key value.'

    db.Entry(inContextVariable).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction("Index");
}
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Grizzly
  • 5,873
  • 8
  • 56
  • 109
  • I would recommend initializing your mappings on spin up rather than on use. We normally run the `AutoMapperConfig.Configure()` method in Startup.cs and that method initializes the mappings. – nurdyguy Jan 11 '17 at 18:40
  • I think your underlying issue here has more to do with EF than anything. Try creating a view model to house the copy and map from the inContextVariable to the vew model variable. Then you can use that variable elsewhere without worrying about EF. (Assuming that is the goal.) – nurdyguy Jan 11 '17 at 18:46
  • @nurdyguy can you provide code so that I can better understand? – Grizzly Jan 11 '17 at 18:50
  • @nurdyguy so create a view model that has the exact same properties as `ModelName`? – Grizzly Jan 11 '17 at 18:50
  • Show us please ModelName class, It's definitely cause ur foreign-key property after mapping is null – Alex Ovechkin Jan 11 '17 at 18:51
  • @BviLLe_Kid Well, to some extent it depends on what you are trying to accomplish. In that snippet you really aren't doing anything. What are you trying to do? What is the goal of that method? – nurdyguy Jan 11 '17 at 18:52
  • @nurdyguy - maybe he is trying to create a copy – Alex Ovechkin Jan 11 '17 at 18:53
  • @AlexOvechkin that's why I asked. If that is the case then the view model idea is pointless. If he wants to create a copy and add that to the db then AutoMapper is totally unnecessary. – nurdyguy Jan 11 '17 at 18:55
  • @nurdyguy Basically, I have another `if` statement (not shown here) to check if the user is entering a `stringProperty` that already exists in the database.. so, because of that... I have to write this `if` statement to say, if the `stringProperty` that is already in the database equals the `stringProperty` that the user is entering then allow it to be saved – Grizzly Jan 11 '17 at 18:56
  • @nurdyguy but once the user hits 'Save'.. I get an error saying `because another entity of the same type already has the same primary key value`, hence my efforts to find a resolution.. leading me to automapper – Grizzly Jan 11 '17 at 18:57
  • @BviLLe_Kid Check out this post and see if that helps http://stackoverflow.com/questions/25720803/entity-framework-6-clone-object-except-id – nurdyguy Jan 11 '17 at 18:58
  • This is because u create a copy with the same Primary Key - which is already exists in ur DB. I think u should configure automapper for ur properties - except Id, and then map to ur exisng model (from DB) – Alex Ovechkin Jan 11 '17 at 19:03
  • @nurdyguy check my answer. Is there a way to do that with automapper? I want to learn how to use automapper. – Grizzly Jan 11 '17 at 19:10
  • @AlexOvechkin check my answer. Is there a way to do that with automapper? I want to learn how to use automapper. – Grizzly Jan 11 '17 at 19:14

3 Answers3

2

Here is the basic logic I would use here

Table inContextVariable = db.TableName.Find(value.ID);
if(!inContextVariable.Email.Equals(value.Email, StringComparison.CurrentCultureIgnoreCase))
{
    // user input a new email so make sure it doesn't match anyone else
    if(db.TableName.Any(x => x.Email.Equals(value.Email, StringComparison.CurrentCultureIgnoreCase) && x.ID != value.ID))
    {
        // user's new email DOES match someone else's email so handle it
    }
    else
    {
        // no match so do the update

    }

}

No need to handle the outer else because they didn't change their email so you don't do anything!

Note: You will have a race condition here! If two people are both submitting at close to the same time then it is possible for the check to pass but they both end up with the same email. Like let's say they both change to x@y.com but the second person's check is done before the first person's save. But, such is the life for web apps.

nurdyguy
  • 2,876
  • 3
  • 25
  • 32
  • Any documented way to handle the race condition? Even though, it is highly unlikely for this specific application? – Grizzly Jan 11 '17 at 20:36
  • Well, likeliness is relative to application user population. A large application absolutely would run into this. I think your best bet would be to put a unique constraint on the db column and wrap this in a try-catch. But, I'm sure others would disagree with that. Dude, race conditions suck... – nurdyguy Jan 11 '17 at 20:39
  • hmm, I bet they do.. I guess when I am in charge of building such an application I will look more into it. – Grizzly Jan 11 '17 at 20:42
0

With the help of @nurdyguy I came up with this idea. I created another class called MappingMethods which holds this method:

public static void MapModelName(ModelName inContext, ModelName outOfContext)
{
    inContext.ID = outOfContext.ID;
    inContext.stringProperty= outOfContext.stringProperty;
    inContext.OwnerID = outOfContext.OwnerID; \\ foreign key
    inContext.DateCreated = outOfContext.DateCreated;
}

Then in my Edit Action:

if (db.TableName.Find(value.ID).stringProperty.Equals(value.stringProperty, StringComparison.CurrentCultureIgnoreCase))
{
    var inContextVariable = db.TableName.Find(value.ID);
    MappingMethods.MapModelName(inContextVariable , value);

    db.Entry(inContextVariable).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction("Index");
}

This worked.

Grizzly
  • 5,873
  • 8
  • 56
  • 109
  • @AlexOvechkin explanation? – Grizzly Jan 11 '17 at 19:19
  • @BviLLe_Kid are you trying to save a NEW row to the db or update an existing row? – nurdyguy Jan 11 '17 at 19:20
  • @nurdyguy update an existing row.. this is the `Edit` action, so I am not creating anything new. – Grizzly Jan 11 '17 at 19:21
  • @BviLLe_Kid try http://stackoverflow.com/questions/15336248/entity-framework-5-updating-a-record – nurdyguy Jan 11 '17 at 19:24
  • @nurdyguy but what happens when the user clicks 'save' on the edit page but didn't edit anything? That is what this is for, because like i said above, I have another if statement checking if `stringProperty` already exists in any record in the database.. so without what I have as an answer.. if the user clicked save and didn't edit anything then it would be blocked because it already exists – Grizzly Jan 11 '17 at 19:25
  • This is an `edit` action but they didn't change anything? Then don't do anything, We may be talking in circles and not understanding each other but unless this is an "upsert" then I don't see the point of doing anything when they didn't change anything. – nurdyguy Jan 11 '17 at 19:30
0

The whole idea of automapper is to avoid such methods in your static class. Imagine if u had 100 properties? (just for example). Will u copy-paste obj1.prop1 = obj2.prop1?

First of all I would suggest to create a view-model - smth. that will represent a view of your model. Yes, you can create a copy of object with automapper, but it wasn't designed for it.

Try to do smth like this:

  1. create a view model - it can be the same as your model or not - it all depends on you (bth it's not a good practice to pass your DB model into views)
  2. Map your model to view-model and virce-verce, like this:

    Mapper.CreateMap() .ForMember(d => d.DateCreate, d => d.MapFrom(x => x.DateCreated));

Alex Ovechkin
  • 810
  • 6
  • 20
  • I 100% agree with your explanation of AutoMapper and it's purpose. AutoMapper is fantastic for this kind of thing. It is just totally unnecessary for the OP's actual problem. – nurdyguy Jan 11 '17 at 19:32
  • @nurdyguy I don't think we are on the same page..... let me update my question with the `if` statement that I am trying to explain. – Grizzly Jan 11 '17 at 19:33
  • @BviLLe_Kid OHH!!! lol... We just need to check that the only time the string matches is when the id matches. Just add to your `.Any(...)`. So something like `.Any(x => x.string1 == inputString && x.Id != inputId)` This way you know that the strings matching are from two different people. – nurdyguy Jan 11 '17 at 19:52
  • @nurdyguy and just delete the entire `if` statement that contains the mapping method? – Grizzly Jan 11 '17 at 20:08
  • @nurdyguy that worked! haha took a while to explain but worth it! Thank you! – Grizzly Jan 11 '17 at 20:14
  • Give me a couple of minutes and I'll write up how I would do the overall logic. – nurdyguy Jan 11 '17 at 20:15
  • @nurdyguy yeah, since there are answers posted... I can't delete the question.. so if you post one I will accept it – Grizzly Jan 11 '17 at 20:15