Created
July 25, 2017 17:07
-
-
Save kalebpederson/4c4e8b6f8ff5574405bd9c5c362ec477 to your computer and use it in GitHub Desktop.
Queries involving DATETIME behave differently between SQL Server versions 2013 & 2016
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Configuration; | |
using System.Data.Entity.Infrastructure; | |
using System.Data.SqlClient; | |
using System.Linq; | |
using NUnit.Framework; | |
namespace DbEfConcurrencyTests | |
{ | |
[TestFixture] | |
public class EfConcurrencyTests | |
{ | |
[TestCase("sqlserver2013", 0)] | |
[TestCase("sqlserver2013", 1)] | |
[TestCase("sqlserver2013", 2)] | |
[TestCase("sqlserver2013", 3)] | |
[TestCase("sqlserver2013", 4)] | |
[TestCase("sqlserver2013", 5)] | |
[TestCase("sqlserver2013", 6)] | |
[TestCase("sqlserver2013", 7)] | |
[TestCase("sqlserver2013", 8)] | |
[TestCase("sqlserver2013", 9)] | |
[TestCase("sqlserver2016", 0)] | |
[TestCase("sqlserver2016", 1)] | |
[TestCase("sqlserver2016", 2)] | |
[TestCase("sqlserver2016", 3)] | |
[TestCase("sqlserver2016", 4)] | |
[TestCase("sqlserver2016", 5)] | |
[TestCase("sqlserver2016", 6)] | |
[TestCase("sqlserver2016", 7)] | |
[TestCase("sqlserver2016", 8)] | |
[TestCase("sqlserver2016", 9)] | |
public void Can_update_a_record_whose_concurrency_field_has_been_modified_in_the_non_disconnected_state(string connectionName, int endTickDigit) | |
{ | |
var expectedId = Guid.NewGuid().ToString(); | |
var ticks = DateTime.Now.Ticks; | |
var creationTime = new DateTime(ticks - (ticks % 100000) + endTickDigit * 10000); | |
var sampleObject = new SampleObject | |
{ | |
Id = expectedId, | |
ModifiedAt = creationTime | |
}; | |
Console.WriteLine($"Sample, before DB storage: ModifiedAt.Ticks={sampleObject.ModifiedAt?.Ticks}, Id = {sampleObject.Id}"); | |
WriteObject(connectionName, sampleObject); | |
var expectedModifiedAt = DateTime.Now.AddSeconds(1); | |
var connectionString = GetConnectionString(connectionName); | |
using (var connection = new SqlConnection(connectionString)) | |
using (var context = new TestingContext<SampleObject>(connection)) | |
{ | |
var sample1 = context.Values.Find(expectedId); | |
Console.WriteLine($"Sample1, after DB retrieval: ModifiedAt.Ticks={sample1.ModifiedAt?.Ticks}, Id = {sample1.Id}"); | |
sample1.ModifiedAt = expectedModifiedAt; | |
Console.WriteLine($"Sample1, after modification: ModifiedAt.Ticks={sample1.ModifiedAt?.Ticks}, Id = {sample1.Id}"); | |
using (var innerContext = new TestingContext<SampleObject>(connection)) | |
{ | |
var sample2 = innerContext.Values.Find(expectedId); | |
Console.WriteLine($"Sample2, after DB retrieval: ModifiedAt.Ticks={sample2.ModifiedAt?.Ticks}, Id = {sample2.Id}"); | |
sample2.ModifiedAt = DateTime.Now.AddSeconds(14); | |
Console.WriteLine($"Sample2, after modification: ModifiedAt.Ticks={sample2.ModifiedAt?.Ticks}, Id = {sample2.Id}"); | |
innerContext.SaveChanges(); | |
Console.WriteLine("Sample2 saved to DB"); | |
} | |
Console.WriteLine("Sample1 about to saved to DB"); | |
Assert.That( | |
() => context.SaveChanges(), | |
Throws.Exception.TypeOf<DbUpdateConcurrencyException>()); | |
} | |
} | |
[TestCase("sqlserver2013", 0)] | |
[TestCase("sqlserver2013", 1)] | |
[TestCase("sqlserver2013", 2)] | |
[TestCase("sqlserver2013", 3)] | |
[TestCase("sqlserver2013", 4)] | |
[TestCase("sqlserver2013", 5)] | |
[TestCase("sqlserver2013", 6)] | |
[TestCase("sqlserver2013", 7)] | |
[TestCase("sqlserver2013", 8)] | |
[TestCase("sqlserver2013", 9)] | |
[TestCase("sqlserver2016", 0)] | |
[TestCase("sqlserver2016", 1)] | |
[TestCase("sqlserver2016", 2)] | |
[TestCase("sqlserver2016", 3)] | |
[TestCase("sqlserver2016", 4)] | |
[TestCase("sqlserver2016", 5)] | |
[TestCase("sqlserver2016", 6)] | |
[TestCase("sqlserver2016", 7)] | |
[TestCase("sqlserver2016", 8)] | |
[TestCase("sqlserver2016", 9)] | |
public void Can_return_a_record_based_on_a_DateTime_field(string connectionName, int endTickDigit) | |
{ | |
var expectedId = Guid.NewGuid().ToString(); | |
var ticks = DateTime.Now.Ticks; | |
var creationTime = new DateTime(ticks - (ticks % 100000) + endTickDigit * 10000); | |
var sampleObject = new SampleObject | |
{ | |
Id = expectedId, | |
ModifiedAt = creationTime | |
}; | |
WriteObject(connectionName, sampleObject); | |
SampleObject sample1; | |
var connectionString = GetConnectionString(connectionName); | |
using (var connection = new SqlConnection(connectionString)) | |
using (var context = new TestingContext<SampleObject>(connection)) | |
{ | |
sample1 = context.Values.First(x => x.ModifiedAt == creationTime); | |
Assert.That(sample1, Is.Not.Null); | |
} | |
} | |
private static void WriteObject(string connectionName, SampleObject sampleObject) | |
{ | |
var connectionString = GetConnectionString(connectionName); | |
using (var connection = new SqlConnection(connectionString)) | |
using (var context = new TestingContext<SampleObject>(connection)) | |
{ | |
context.Values.Add(sampleObject); | |
context.SaveChanges(); | |
} | |
} | |
private static string GetConnectionString(string connectionName) | |
{ | |
return ConfigurationManager.ConnectionStrings[connectionName].ConnectionString; | |
} | |
} | |
public class SampleObject | |
{ | |
public string Id { get; set; } | |
[ConcurrencyCheck] | |
[Column(TypeName="datetime")] | |
public DateTime? ModifiedAt { get; set; } | |
} | |
public class TestingContext<T> : DbContext where T: class | |
{ | |
public DbSet<T> Values { get; set; } | |
public TestingContext(DbConnection connection) : base(connection, false) | |
{ | |
Database.Log = Console.WriteLine; | |
Database.SetInitializer<TestingContext<T>>(null); | |
} | |
protected override void OnModelCreating(DbModelBuilder modelBuilder) | |
{ | |
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); | |
modelBuilder.Properties<DateTime>().Configure(p => p.HasColumnType("datetime")); | |
} | |
} | |
} | |
// Nuget package dependencies: NUnit-3.7.1, EntityFramework-6.1.3 | |
/* | |
// App.Config content: | |
<connectionStrings> | |
<clear /> | |
<!-- | |
this should point to a SQL Server 2013 DB (though it's my understanding the behavior on a SQL Server 2014 | |
server is equivalent) | |
--> | |
<add name="sqlserver2013" providerName="System.Data.ProviderName" connectionString="...;Data Source=MySqlServer2013" /> | |
<!-- this should point to a SQL Server 2016 DB --> | |
<add name="sqlserver2016" providerName="System.Data.ProviderName" connectionString="...;Data Source=MySqlServer2016" /> | |
</connectionStrings> | |
// */ | |
/* | |
// SQL Server Schema | |
SET ANSI_NULLS ON | |
GO | |
SET QUOTED_IDENTIFIER ON | |
GO | |
CREATE TABLE [dbo].[SampleObject]( | |
[Id] [varchar](50) NOT NULL, | |
[ModifiedAt] [datetime] NULL, | |
CONSTRAINT [PK_SampleObject] PRIMARY KEY CLUSTERED | |
( | |
[Id] ASC | |
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] | |
) ON [PRIMARY] | |
GO | |
// */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment