Skip to content

Instantly share code, notes, and snippets.

@kalebpederson
Created July 25, 2017 17:07
Show Gist options
  • Save kalebpederson/4c4e8b6f8ff5574405bd9c5c362ec477 to your computer and use it in GitHub Desktop.
Save kalebpederson/4c4e8b6f8ff5574405bd9c5c362ec477 to your computer and use it in GitHub Desktop.
Queries involving DATETIME behave differently between SQL Server versions 2013 & 2016
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