2016-02-06 3 views
6

का उपयोग करता है मेरे पास टाइमस्टैम्प (कंसुरेंसी टोकन) कॉलम वाला मॉडल है। मैं एक एकीकरण परीक्षण लिखने की कोशिश कर रहा हूं जहां मैं जांच करता हूं कि यह काम करता है जैसा कि मैं उम्मीद करता हूं लेकिन बिना किसी सफलता के। मेरा परीक्षणईएफ कोर - सहेजने से पहले टाइमस्टैम्प सेट करें अभी भी पुराने मान

  1. उस इकाई को प्राप्त करें जिसे वेब एपीआई से एचटीपी क्लाइंट कॉल के साथ अपडेट किया जाना चाहिए।
  2. प्रसंग को सीधे अनुरोध करें और एक ही इकाई
  3. बदलें इकाई पर एक संपत्ति कदम 2.
  4. सहेजें इकाई चरण में अद्यतन 3.
  5. बदलें कदम से इकाई पर एक संपत्ति से प्राप्त 1.
  6. वेब एपीआई के लिए एचटीपी क्लाइंट के साथ नई इकाई के साथ एक पुट अनुरोध भेजें।
  7. मेरे वेब एपीआई में मुझे पहले डेटाबेस से इकाई मिलती है, ग्राहक से प्राप्त संपत्ति से संपत्ति और टाइमस्टैम्प मान सेट करता है। अब एपीआई नियंत्रक में मेरी इकाई ऑब्जेक्ट में डेटाबेस में से एक की तुलना में एक अलग टाइमस्टैम्प मान है। अब मैं उम्मीद करता हूं कि savechanges असफल हो जाएंगे, लेकिन ऐसा नहीं है। इसके बजाय यह इकाई को डेटाबेस में सहेजता है और एक नया टाइमस्टैम्प मान उत्पन्न करता है। मैंने जेनरेट क्वेरी देखने के लिए एसक्यूएल सर्वर प्रोफाइलर के साथ जांच की और यह पता चला कि अभी भी पुराने टाइमस्टैम्प मान का उपयोग किया गया है, न कि मैंने अपने एपीआई नियंत्रक में इकाई को सौंपा है।

इसका कारण क्या है? क्या टाइमस्टैम्प के साथ डेटाबेस उत्पन्न मूल्य होने के साथ इसका कोई संबंध नहीं है जो ईएफ को व्यापार परत से किए गए परिवर्तनों को अनदेखा करता है?

पूर्ण परीक्षण आवेदन यहां पाया जा सकता: 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 the original timestamp from the entity and not the assigned and therefore the save succeed which was not intended 
    exec sp_executesql N'SET NOCOUNT OFF; 
    UPDATE[Person] SET[Title] = @p2 
    OUTPUT INSERTED.[Timestamp] 
    WHERE [Id] = @p0 AND[Timestamp] = @p1; 
    ',N'@p0 int,@p1 varbinary(8),@p2 nvarchar(4000)',@p0=21,@p1=0x00000000000007F4,@p2=N'After - should fail' 
+0

कृपया अपनी इकाई फ्रेमवर्क मैपिंग कोड शामिल करें, शायद इसके साथ कुछ करने के लिए कुछ है। क्या आप एसक्यूएल प्रोफाइलर से उत्पन्न और निष्पादित क्वेरी भी पोस्ट कर सकते हैं? – Igor

उत्तर

4

संपादित 4 - फिक्स

मैं GitHub रेपो साइट, issue 4512 पर एक सदस्य से वापस सुना। आपको इकाई पर मूल मान अपडेट करना होगा। ऐसा किया जा सकता है।

var passedInTimestamp = new byte[] { 0, 0, 0, 0, 0, 0, 0, 120 }; // a hard coded value but normally included in a postback 
var entryProp = db.Entry(person).Property(u => u.Timestamp); 
entryProp.OriginalValue = passedInTimestamp; 

मैं मूल इकाई परीक्षण कि विफल जहां तुम और मैं DbUpdateConcurrencyException फेंक दिया जा रहा है, अब यह काम करता है के रूप में उम्मीद नहीं मिल सका अद्यतन किया है।

मैं इतना है कि अंतर्निहित एसक्यूएल कि उत्पन्न होता है कि यह करने के लिए इसी तरह के बर्ताव करता है मूल मूल्य जब स्तंभ Timestamp या तो IsConcurrencyToken के रूप में चिह्नित किया गया है के बजाय नया मान का उपयोग करता है अगर वे एक परिवर्तन कर सकते हैं पूछने के लिए GitHub टिकट अद्यतन करेगा इकाई फ्रेमवर्क के पिछले संस्करण।

अभी के लिए यह अलग-अलग इकाइयों के साथ ऐसा करने का तरीका प्रतीत होता है।


# संपादित 3

धन्यवाद, मुझे लगता है कि याद किया। एक बार और अधिक डिबगिंग के बाद मैं इस मुद्दे को पूरी तरह से समझता हूं, हालांकि ऐसा क्यों नहीं हो रहा है। हालांकि, हमें शायद वेब एपीआई लेना चाहिए, हालांकि, कम चलने वाले हिस्सों और मुझे नहीं लगता कि ईएफ कोर और वेब एपीआई के बीच सीधी निर्भरता है। मैंने इस मुद्दे को निम्नलिखित परीक्षणों के साथ पुन: उत्पन्न किया है जो इस मुद्दे को दर्शाते हैं। मैं इसे एक बग कहने में संकोच कर रहा हूं क्योंकि ईएफ कोर को timestamp में पारित करने के लिए ईएफ कोर को मजबूर करने के लिए सम्मेलन ईएफ 6 के बाद बदल गया है।

मैंने प्रोजेक्ट की गिटहब साइट पर न्यूनतम कोड और created an issue/question का एक पूरा सेट बनाया है। संदर्भ के लिए मैं एक बार नीचे परीक्षण शामिल करूंगा। जैसे ही मैं वापस सुनता हूं, मैं इस जवाब पर वापस पोस्ट करूंगा और आपको बता दूंगा।

निर्भरता

  • SQL Server 2012
  • एफई कोर
    • EntityFramework.Commands 7.0.0-RC1 फाइनल
    • EntityFramework.MicrosoftSqlServer 7.0.0-RC1 फाइनल

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 INTO Person (title) values('user number 1') 

इकाई

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

    public String Title { get; set; } 

    // [Timestamp], tried both with & without annotation 
    public byte[] Timestamp { get; set; } 
} 

Db प्रसंग

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); 
    } 
} 

यूनिट टेस्ट

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] // this 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 the database manually after we have a person instance 
      using (var connection = new System.Data.SqlClient.SqlConnection(dbConnectionString)) 
      { 
       var command = connection.CreateCommand(); 
       command.CommandText = "update person set title = 'changed title' where 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(); 

      // This emulates post back where the timestamp is passed in from the web page 
      // the Person entity attached dbcontext does have the latest timestamp value but 
      // it needs to be changed to what was posted 
      // this way the user would see that something has changed between the time that their screen initially loaded and the time they posted the form back 
      var passedInTimestamp = new byte[] { 0, 0, 0, 0, 0, 0, 0, 120 }; // a hard coded value but normally included in a postback 
      //person.Timestamp = passedInTimestamp; 
      var entry = db.Entry(person).Property(u => u.Timestamp); 
      entry.OriginalValue = passedInTimestamp; 
      try 
      { 
       await db.SaveChangesAsync(); // EF ignores the set Timestamp value and uses its own value in the outputed sql 
       throw new Exception("should have thrown DbUpdateConcurrencyException"); 
      } 
      catch (DbUpdateConcurrencyException) 
      { 
      } 
     } 
    } 
} 
+0

बेशक मैंने पुट विधि में ब्रेकपॉइंट सेट किया है और टाइमस्टैम्प की जांच की है और पुष्टि कर सकते हैं कि वे समान नहीं हैं। जब मैं ऑब्जेक्ट को देखता हूं लेकिन क्वेरी में उपयोग नहीं किया जाता है तो वह मान जिसे मैं व्यक्ति को सौंपा गया है वास्तव में असाइन किया जाता है। मैंने एक वर्ग का उपयोग नहीं किया है, मैंने इसे यहां एक साथ रखा है। मैं जल्द ही परीक्षण आवेदन के साथ एक जिथब रेपो से लिंक करूंगा। – Abris

+0

https://github.com/Abrissirba/EfTimestampBug – Abris

+0

@ एब्रिस - धन्यवाद, जिसने काफी मदद की। मैं आपकी समस्या को ठीक करने में सक्षम था, अद्यतन कोड देखें। एक बार जब आप यह छोटा परिवर्तन कर लेंगे तो आपके यूनिट परीक्षणों को भी काम करना चाहिए। – Igor

0

माइक्रोसॉफ्ट ने Handling concurrency conflicts - EF Core with ASP.NET Core MVC tutorial में इसके लिए अपने ट्यूटोरियल को अपडेट किया है। यह विशेष रूप से निम्नलिखित के बारे में अपडेट राज्यों:

इससे पहले कि आप SaveChanges कहते हैं, आप इकाई के लिए OriginalValues संग्रह में है कि मूल RowVersion संपत्ति के मूल्य पर रखना होगा।

_context.Entry(entityToUpdate).Property("RowVersion").OriginalValue = rowVersion; 

फिर जब इकाई की रूपरेखा एक एसक्यूएल अद्यतन आदेश बनाता है, कि आदेश एक कहां खंड है कि एक पंक्ति मूल RowVersion मूल्य होता है के लिए लग रहा है शामिल होंगे। यदि अद्यतन कमांड द्वारा कोई पंक्ति प्रभावित नहीं होती है (कोई पंक्तियों में मूल RowVersion मान नहीं है), इकाई फ्रेमवर्क DbUpdateConcurrencyException अपवाद फेंकता है।

संबंधित मुद्दे