Entity Framework 6 Upsert

c# entity-framework-6

Question

I'm trying to get a upsert working with Entity Framework. I have the following code that is throwing an error:

using (var db = new Entities.DB.DConn())
{
    //...
    foreach (Account account in accounts)
    {
        Entities.DB.Account dlAccount = new Entities.DB.Account();
        dlAccount.GId = dlG.Id;
        dlAccount.AccountName = account.NameAtFI;
        dlAccount.AccountNumber = account.AcctNumber;
        dlAccount.AcctType = account.AcctType;
        dlAccount.AsOfDate = account.DateCreated;
        dlAccount.IsDeleted = false;
        dlAccount.DateModified = DateTime.UtcNow.ToUniversalTime();

        db.Entry(dlAccount).State = ((dlAccount.GId == dlG.Id) ? EntityState.Modified : EntityState.Added);

        db.SaveChanges();
    }
}

Exception:

Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded.

Essentially all I want to do is update the record if dlAccount.GId == dlG.Id or insert it if it does not exist. The following code achieves what i want without using EntityState:

using (var db = new Entities.DB.DConn())
{
    //...
    foreach (Account account in accounts)
    {
        bool isNewRecord = false;
        Entities.DB.Account dlAccount = new Entities.DB.Account();
        Entities.DB.Account exisitngAcct = db.Accounts.Where(x => x.GId == dlG.Id).FirstOrDefault(); //x.GId is NOT ad primary key
        if (exisitngAcct != null)
        {
            dlAccount = exisitngAcct;
            isNewRecord = true;
        }

        dlAccount.GId = dlG.Id;
        dlAccount.AccountName = account.NameAtFI;
        dlAccount.AccountNumber = account.AcctNumber;
        dlAccount.AcctType = account.AcctType;
        dlAccount.AsOfDate = account.DateCreated;
        dlAccount.IsDeleted = false;
        dlAccount.DateModified = DateTime.UtcNow.ToUniversalTime();

        if (isNewRecord)
        {
            dldb.Accounts.Add(dlAccount);
        }

        db.SaveChanges();
    }
}

Can anyone see anything that I'm perhaps doing wrong here? I would really like to get this working and avoid having to use over bloated code like above.

TIA

1
1
7/13/2017 3:53:38 PM

Accepted Answer

First I should point out that the logic in the (non-EntityState) example you posted doesnt look like I would expect it to - at least based on my understanding, which may be wrong :)

*Disclaimer - I've hacked this out in a text editor, please excuse any bugs.

If we take this as your requirement:

Essentially all I want to do is update the record if dlAccount.GId == dlG.Id or insert it if it does not exist.

Then I would expect the non-EntityState version to look like this:

using (var db = new Entities.DB.DConn())
{
    //...
    foreach (Account account in accounts)
    {
        Entities.DB.Account dlAccount = null;

        Entities.DB.Account exisitngAcct = db.Accounts.Where(x => x.GId == dlG.Id).FirstOrDefault(); //x.GId is NOT ad primary key
        if (exisitngAcct != null)
        {
            //If there is an EXISTING account, it will already be tracked by EF so no need to attach it.
            dlAccount = exisitngAcct;                
        }
        else
        {
            //No account exists, so we need to create one, and ADD it to our EF context as a new Entity 
            dlAccount = new Entities.DB.Account();
            db.Accounts.Add(dlAccount);
        }

        dlAccount.GId = dlG.Id;
        dlAccount.AccountName = account.NameAtFI;
        dlAccount.AccountNumber = account.AcctNumber;
        dlAccount.AcctType = account.AcctType;
        dlAccount.AsOfDate = account.DateCreated;
        dlAccount.IsDeleted = false;
        dlAccount.DateModified = DateTime.UtcNow.ToUniversalTime();

        db.SaveChanges();
    }
}

Assuming the above is what you need, and also assuming that we have a good reason not to just use EF tracking as above, then manually handling EF state would look something like this:

using (var db = new Entities.DB.DConn())
{
    //...
    foreach (Account account in accounts)
    {
        Entities.DB.Account exisitngAcct = db.Accounts.FirstOrDefault(x => x.GId == dlG.Id).FirstOrDefault(); //x.GId is NOT ad primary key

        //NB. Since we're already pulling up the record with EF, there is *probably* no measurable advantage in not just using EF tracking at this point (unless this is a HUUUGE list of objects)
        //    in which case we should use the .AsNoTracking() modifier when we load the records (and they should be loaded in batches/all at once, to reduce DB hits)

        Entities.DB.Account dlAccount = new Entities.DB.Account();
        if(exisitngAcct == null)
        {
            db.Entry(dlAccount).State = EntityState.Added;
        }
        else
        {
            dlAccount.Id = exisitngAcct.Id; //We have to set the PK, so that EF knows which object to update
            db.Entry(dlAccount).State = EntityState.Modified;
        }

        dlAccount.GId = dlG.Id;
        dlAccount.AccountName = account.NameAtFI;
        dlAccount.AccountNumber = account.AcctNumber;
        dlAccount.AcctType = account.AcctType;
        dlAccount.AsOfDate = account.DateCreated;
        dlAccount.IsDeleted = false;
        dlAccount.DateModified = DateTime.UtcNow.ToUniversalTime();

        db.SaveChanges();
    }
}
2
7/13/2017 5:03:59 PM


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