The property 'AccountNumber' is part of the object's key information and cannot be modified

c# entity-framework entity-framework-6

Question

I'm using code first Entity Framework (code-first) in my application.

When I try to add a NEW object and save it to the database, EF is throwing this error.

The property 'AccountNumber' is part of the object's key information and cannot be modified.

First thing that is confusing about this error is AccountNumber is not a primary key in that table.

After reading a few posts to solve this, I try to create a brand new object just before it saves it like this:

public void CreateCustomerLookUp(CustomerLookUp customerLookUp)
    {
        //To avoid EF specific error recreating object using NEW - wierd!
        //http://stackoverflow.com/questions/3187963/the-property-id-is-part-of-the-objects-key-information-and-cannot-be-modified

        customerLookUp.LastUpdated = DateTime.Now;

        var encryptedCustomerLookUp = EncryptCustomerLookUp(customerLookUp);

        var newCustomerLookup = new CustomerLookUp()
        {
            AccountNumber = encryptedCustomerLookUp.AccountNumber,
            EmailAddress = encryptedCustomerLookUp.EmailAddress,
            MobileNumber = encryptedCustomerLookUp.MobileNumber,
            UmbracoMemberId = encryptedCustomerLookUp.UmbracoMemberId,
            UpdateCode = encryptedCustomerLookUp.UpdateCode,
            UpdateNotification = encryptedCustomerLookUp.UpdateNotification
        };

        customerDataContext.Entry(newCustomerLookup).State = EntityState.Added;//<< Throwing error here!
        customerDataContext.SaveChanges();
    }

I have also tried

customerDataContext.CustomerLookUps.Add(newCustomerLookup);

Instead of

customerDataContext.Entry(newCustomerLookup).State = EntityState.Added;

But I'm still getting the error. This is what my CustomerLookup entity looks like, I can't figure out why EF thinks AccountNumber is a Key its not. (I was thinking maybe primary key and key are two different things?)

    public class CustomerLookUp
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [StringLength(256)]
    public string AccountNumber { get; set; }

    [StringLength(256)]
    public string MobileNumber { get; set; }
    [StringLength(256)]
    public string EmailAddress { get; set; }
    [StringLength(256)]
    public string UpdateCode { get; set; }

    [StringLength(256)]
    public string UmbracoMemberId { get; set; }

    public bool UpdateNotification { get; set; }

    public DateTime LastUpdated { get; internal set; }
}

This is my context class only 2 DB-sets THAT ARE NOT linked. I have looked in the DB to see what gets generated and its two tables as expected. The other Entity "Customer" does have AccountNumber as the PK but I am not trying to save to that table.

public partial class CustomerContext : DbContext
{
    public CustomerContext()
        : base("name=CustomerContext")
    {
    }

    public DbSet<Customer> Customers { get; set; }
    public DbSet<CustomerLookUp> CustomerLookUps { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    }



}

For completeness this is the Customer class. (This has AccountNumber as PK, but this is in a completely different table, and not one I am trying to save in too.

public class Customer
{
    [Key]
    [Required]
    [StringLength(256)]
    public string AccountNumber { get; set; }

    [Required]
    [StringLength(256)]
    public string SupplyPostCode { get; set; }

    [StringLength(256)]
    public string Title { get; set; }

    [StringLength(256)]
    public string Forenames { get; set; }

    [StringLength(256)]
    public string Name { get; set; }



}

UPDATE: I have made progress, but not totally solved the problem. What I decided to do was Rename the columns so I had two different names in each table for AccountNumber in the CustomerLookUp class I changed the name to AccountNumberX then run the cure again. I still got the same error. Which means for sure the problem was nothing to do with the entity I was trying to save. EF was complaining about the Customer Class.

Within the business logic that ultimately calls the CreateCustomerLookUp method I am retrieving a customer record and Decrypting it, I need to do this to read the plain text values, but I am not asking EF to save these changes.

So what is happening is when I am saving the newCustomerLookup object it is also trying to save the Decrypted version of the customer record that I retrieved earlier.

So the real question is, how do I tell EF I don't want it to save the changes made to that object? I only want to save the new newCustomerLookup object.

1
0
3/2/2017 12:28:19 PM

Accepted Answer

As explained in the answer by 'Tone' this error occurs because EF is trying to update all known entities that have changed, even if you do not explicitly call:

customerDataContext.Entry(newCustomerLookup).State = EntityState.Added;
customerDataContext.SaveChanges();

In my case the data that is stored in the DB is encrypted. In my business logic I need to un-encrypt a customer, perform some business logic then update a customerLookUp record. By un-encrypting the customer record I have effectively changed it, which is what causes the error when I try and save the customerLookup record.

Solution:

There are a few different ways to solve this problem. One way is to create a second data-context object. You would use the first one to get the record and un-encrypt it, and then the second one will be used to save the entity to the db. Though this works, I find it to be a bit of a messy solution.

Instead of un-encrypting the record that comes from the dbcontext, I copied the data as it is in to a new object. Then I un-encrypt that new object, this way I never change the record, so when I call saveChanges() the only changes EF will make are to the record I explicitly changed.

This concept is known as the "Data Transfer Object Pattern" you can read more about it here:

https://docs.microsoft.com/en-us/aspnet/web-api/overview/data/using-web-api-with-entity-framework/part-5

https://www.exceptionnotfound.net/entity-framework-and-wcf-mapping-entities-to-dtos-with-automapper/

0
3/5/2017 6:38:36 PM

Popular Answer

Based on your update, you need to look at the lifetime of your DbContext. A DbContext will by default, track changes in any entities it's aware of then try to save those changes when you call SaveChanges().

So what looks to be happening in this case is you're defining a DbContext somewhere, working with your Customer, then working with your CustomerLookup, all while the DbContext is active and aware of your entities (e.g. it will be aware of any entity you retrieve from the database). Then when you call SaveChanges() it's trying to save everything it knows has changed.

You generally want to create and dispose DbContexts only as you need them. They're lightweight and you'll typically want them to last for a unit of work (whatever that may be in your app). Get into the habit enclosing their use in using blocks to keep things clean. So your method might look something like:

public void CreateCustomerLookUp(CustomerLookUp customerLookUp)
{
    using (var context = new CustomerContext())
    {
        customerLookUp.LastUpdated = DateTime.Now;

        var encryptedCustomerLookUp = EncryptCustomerLookUp(customerLookUp);

        var newCustomerLookup = new CustomerLookUp()
        {
            AccountNumber = encryptedCustomerLookUp.AccountNumber,
            EmailAddress = encryptedCustomerLookUp.EmailAddress,
            MobileNumber = encryptedCustomerLookUp.MobileNumber,
            UmbracoMemberId = encryptedCustomerLookUp.UmbracoMemberId,
            UpdateCode = encryptedCustomerLookUp.UpdateCode,
            UpdateNotification = encryptedCustomerLookUp.UpdateNotification
        };

        context.Entry(customerLookUp).State = EntityState.Modified;
        context.CustomerLookUps.Add(newCustomerLookup);
        context.SaveChanges();
    }
}

Notes:

  1. You'll also need to make changes elsewhere in your code to similarly localize DbContexts. Making the above change alone will result in 2 DbContexts being active so you may well still run into other tracking issues.

  2. You could potentially restrict the scope even further within the method with a bit of refactoring. I've enclosed the whole method as I see you're updating 2 entities (customerLookup and newCustomerLookup) and I don't know what you're doing in EncryptCustomerLookup.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow