entity framework - EF Core - Set Timestamp before save still uses the old value -


i have model timestamp (concurrency token) column. i'm trying write integration test check works expect no success. test looks following

  1. get entity should updated web api httpclient call.
  2. make request directly context , same entity
  3. change property on entity step 2.
  4. save entity updated in step 3.
  5. change property on entity step 1.
  6. send put request new entity httpclient web api.
  7. in web api first entity database, sets property , timestamp value 1 got client. entity object in api controller has different timestamp value 1 in database. expect savechanges fail, doesn't. instead saves entity database , generates new timestamp value. checked sql server profiler see generated query , turns out still use old timestamp value , not 1 assigned entity in api controller.

what reason this? have timestamp being database generated value makes ef ignore changes made business layer?

the full test application can found here: https://github.com/abrissirba/eftimestampbug

    public class basemodel     {         [timestamp]         public byte[] timestamp { get; set; }     }      public class person : basemodel     {         public int id { get; set; }          public string title { get; set; }     }      public class context : dbcontext     {         public context()         {}          public context(dbcontextoptions options) : base(options)         {}          public dbset<person> persons{ get; set; }     }      protected override void buildmodel(modelbuilder modelbuilder)     {         modelbuilder             .hasannotation("productversion", "7.0.0-rc1-16348")             .hasannotation("sqlserver:valuegenerationstrategy", sqlservervaluegenerationstrategy.identitycolumn);          modelbuilder.entity("eftimestampbug.models.person", b =>             {                 b.property<int>("id")                     .valuegeneratedonadd();                  b.property<byte[]>("timestamp")                     .isconcurrencytoken()                     .valuegeneratedonaddorupdate();                  b.property<string>("title");                  b.haskey("id");             });     }      // put api/values/5     [httpput("{id}")]     public person put(int id, [frombody]person persondto)     {         // 7         var person = db.persons.singleordefault(x => x.id == id);         person.title = persondto.title;         person.timestamp = persondto.timestamp;         db.savechanges();         return person;     }      [fact]     public async task fail_when_timestamp_differs()     {         using (var client = server.createclient().acceptjson())         {             await client.postasjsonasync(apiendpoint, persons[0]);             // 1             var getresponse = await client.getasync(apiendpoint);             var fetched = await getresponse.content.readasjsonasync<list<person>>();              assert.true(getresponse.issuccessstatuscode);             assert.notempty(fetched);              var person = fetched.first();             // 2             var fromdb = await db.persons.singleordefaultasync(x => x.id == person.id);             // 3             fromdb.title = "in between";             // 4             await db.savechangesasync();               // 5             person.title = "after - should fail";             // 6             var postresponse = await client.putasjsonasync(apiendpoint + person.id, person);             var created = await postresponse.content.readasjsonasync<person>();              assert.false(postresponse.issuccessstatuscode);         }     }       // generated sql - @p1 has original timestamp entity , not assigned , therefore save succeed not intended     exec sp_executesql n'set nocount off;     update[person] set[title] = @p2     output inserted.[timestamp]     [id] = @p0 and[timestamp] = @p1;     ',n'@p0 int,@p1 varbinary(8),@p2 nvarchar(4000)',@p0=21,@p1=0x00000000000007f4,@p2=n'after - should fail' 

edit 4 - fix

i heard member on github repo site, issue 4512. have update original value on entity. can done so.

var passedintimestamp = new byte[] { 0, 0, 0, 0, 0, 0, 0, 120 };  // hard coded value included in postback var entryprop = db.entry(person).property(u => u.timestamp); entryprop.originalvalue = passedintimestamp; 

i have updated original unit test failed , not dbupdateconcurrencyexception thrown, works expected.

i update github ticket ask if can make change underlying sql generated uses new value instead of original value when column marked timestamp or isconcurrencytoken behaves similar previous versions of entity framework.

for though appears way go doing detached entities.


edit #3

thank you, missed that. after more debugging once more understand issue although not why occurring. should take web api out of though, less moving parts , not think there direct dependency between ef core , web api. have reproduced issue following tests illustrate issue. i hesitant call bug maybe convention forcing ef core use passed in timestamp value has changed since ef6.

i have created complete set of working minimal code , created issue/question on project's github site. include test once more below reference. hear post on answer , let know.

dependencies

  • sql server 2012
  • ef core
    • entityframework.commands 7.0.0-rc1-final
    • entityframework.microsoftsqlserver 7.0.0-rc1-final

ddl

create table [dbo].[person](     [id] [int] identity not null,     [title] [varchar](50) not null,     [timestamp] [rowversion] not null,  constraint [pk_person] primary key clustered  (     [id] asc )) insert person (title) values('user number 1') 

entity

public class person {     public int id { get; set; }      public string title { get; set; }      // [timestamp], tried both & without annotation     public byte[] timestamp { get; set; } } 

db context

public class context : dbcontext {     public context(dbcontextoptions options)         : base(options)     {     }      public dbset<person> persons { get; set; }      protected override void onmodelcreating(modelbuilder modelbuilder)     {         modelbuilder.entity<person>().haskey(x => x.id);          modelbuilder.entity<person>().property(x => x.id)             .usesqlserveridentitycolumn()             .valuegeneratedonadd()             .forsqlserverhascolumnname("id");          modelbuilder.entity<person>().property(x => x.title)             .forsqlserverhascolumnname("title");          modelbuilder.entity<person>().property(x => x.timestamp)             .isconcurrencytoken(true)             .valuegeneratedonaddorupdate()             .forsqlserverhascolumnname("timestamp");          base.onmodelcreating(modelbuilder);     } } 

unit test

public class unittest {     private string dbconnectionstring = "dbconnectionstringorconnectionname";     public eftimestampbug.models.context createcontext()     {         var options = new dbcontextoptionsbuilder();         options.usesqlserver(dbconnectionstring);         return new eftimestampbug.models.context(options.options);     }      [fact] // test passes     public async task timestampchangedexternally()     {         using (var db = createcontext())         {             var person = await db.persons.singleasync(x => x.id == 1);             person.title = "update 2 - should fail";              // update database manually after have person instance             using (var connection = new system.data.sqlclient.sqlconnection(dbconnectionstring))             {                 var command = connection.createcommand();                 command.commandtext = "update person set title = 'changed title' id = 1";                 connection.open();                 await command.executenonqueryasync();                 command.dispose();             }              // should throw exception             try             {                 await db.savechangesasync();                 throw new exception("should have thrown exception");             }             catch (dbupdateconcurrencyexception)             {             }         }     }      [fact]     public async task emulateasppostbackwheretimestamphadbeenchanged()     {         using (var db = createcontext())         {             var person = await db.persons.singleasync(x => x.id == 1);             person.title = "update 2 - should fail " + datetime.now.second.tostring();              // emulates post timestamp passed in web page             // person entity attached dbcontext have latest timestamp value             // needs changed posted             // way user see has changed between time screen loaded , time posted form             var passedintimestamp = new byte[] { 0, 0, 0, 0, 0, 0, 0, 120 };  // hard coded value included in postback             //person.timestamp = passedintimestamp;             var entry = db.entry(person).property(u => u.timestamp);             entry.originalvalue = passedintimestamp;             try             {                 await db.savechangesasync(); // ef ignores set timestamp value , uses own value in outputed sql                 throw new exception("should have thrown dbupdateconcurrencyexception");             }             catch (dbupdateconcurrencyexception)             {             }         }     } } 

Comments

Popular posts from this blog

sublimetext3 - what keyboard shortcut is to comment/uncomment for this script tag in sublime -

java - No use of nillable="0" in SOAP Webservice -

ubuntu - Laravel 5.2 quickstart guide gives Not Found Error -