diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs index 6d969cc5813..73e9c6bb4de 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs @@ -438,7 +438,7 @@ private ItemRequestOptions CreateItemRequestOptions(IUpdateEntry entry) return null; } - var etag = entry.GetCurrentValue(etagProperty); + var etag = entry.GetOriginalValue(etagProperty); var converter = etagProperty.GetTypeMapping().Converter; if (converter != null) { diff --git a/test/EFCore.Cosmos.FunctionalTests/F1CosmosFixture.cs b/test/EFCore.Cosmos.FunctionalTests/F1CosmosFixture.cs new file mode 100644 index 00000000000..16ea60b9e1b --- /dev/null +++ b/test/EFCore.Cosmos.FunctionalTests/F1CosmosFixture.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel; +using Microsoft.EntityFrameworkCore.TestUtilities; + +// ReSharper disable InconsistentNaming +namespace Microsoft.EntityFrameworkCore.Cosmos +{ + public class F1CosmosFixture : F1FixtureBase + { + protected override ITestStoreFactory TestStoreFactory + => CosmosTestStoreFactory.Instance; + + public override TestHelpers TestHelpers + => CosmosTestHelpers.Instance; + + protected override void BuildModelExternal(ModelBuilder modelBuilder) + { + base.BuildModelExternal(modelBuilder); + + modelBuilder.Entity( + b => + { + b.Property(e => e.EngineSupplierId).IsConcurrencyToken(false); + b.Property(e => e.Name).IsConcurrencyToken(false); + b.OwnsOne( + e => e.StorageLocation, lb => + { + lb.Property(l => l.Latitude).IsConcurrencyToken(false); + lb.Property(l => l.Longitude).IsConcurrencyToken(false); + }); + }); + + modelBuilder.Entity().Property("Version").IsETagConcurrency(); + modelBuilder.Entity().Property("Version").IsETagConcurrency(); + modelBuilder.Entity().Property("Version").IsETagConcurrency(); + + modelBuilder.Entity( + eb => + { + eb.Property("Version").IsETagConcurrency(); + eb.Property(Sponsor.ClientTokenPropertyName).IsConcurrencyToken(false); + }); + modelBuilder.Entity() + .OwnsOne( + s => s.Details, eb => + { + eb.Property("Version").IsETagConcurrency(); + eb.Property(Sponsor.ClientTokenPropertyName); + }); + } + } +} diff --git a/test/EFCore.Cosmos.FunctionalTests/OptimisticConcurrencyCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/OptimisticConcurrencyCosmosTest.cs new file mode 100644 index 00000000000..b1982c8ab81 --- /dev/null +++ b/test/EFCore.Cosmos.FunctionalTests/OptimisticConcurrencyCosmosTest.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; + +// ReSharper disable InconsistentNaming +namespace Microsoft.EntityFrameworkCore.Cosmos +{ + public class OptimisticConcurrencyCosmosTest : OptimisticConcurrencyTestBase + { + public OptimisticConcurrencyCosmosTest(F1CosmosFixture fixture) + : base(fixture) + { + fixture.Reseed(); + } + + // Non-persisted property in query + // Issue #17670 + public override Task Calling_GetDatabaseValues_on_owned_entity_works(bool async) + => Task.CompletedTask; + + public override Task Calling_Reload_on_owned_entity_works(bool async) + => Task.CompletedTask; + + // Only ETag properties can be used as concurrency tokens + public override Task Concurrency_issue_where_the_FK_is_the_concurrency_token_can_be_handled() + => Task.CompletedTask; + + public override void Nullable_client_side_concurrency_token_can_be_used() + { + } + + // ETag concurrency doesn't work after an item was deleted + public override Task Deleting_the_same_entity_twice_results_in_DbUpdateConcurrencyException() + => Task.CompletedTask; + + public override Task Deleting_then_updating_the_same_entity_results_in_DbUpdateConcurrencyException() + => Task.CompletedTask; + + public override Task Deleting_then_updating_the_same_entity_results_in_DbUpdateConcurrencyException_which_can_be_resolved_with_store_values() + => Task.CompletedTask; + + protected override IDbContextTransaction BeginTransaction(DatabaseFacade facade) => new FakeDbContextTransaction(); + + private class FakeDbContextTransaction : IDbContextTransaction + { + public Guid TransactionId => new Guid(); + + public void Commit() + { + } + + public Task CommitAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; + + public void Dispose() + { + } + + public ValueTask DisposeAsync() => default; + + public void Rollback() + { + } + + public Task RollbackAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; + } + } +} diff --git a/test/EFCore.Relational.Specification.Tests/PropertyEntryTestBase.cs b/test/EFCore.Relational.Specification.Tests/OptimisticConcurrencyRelationalTestBase.cs similarity index 82% rename from test/EFCore.Relational.Specification.Tests/PropertyEntryTestBase.cs rename to test/EFCore.Relational.Specification.Tests/OptimisticConcurrencyRelationalTestBase.cs index 44ecb4406f4..a897cb81a30 100644 --- a/test/EFCore.Relational.Specification.Tests/PropertyEntryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/OptimisticConcurrencyRelationalTestBase.cs @@ -9,13 +9,12 @@ // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore { - public abstract class PropertyEntryTestBase : IClassFixture + public abstract class OptimisticConcurrencyRelationalTestBase : OptimisticConcurrencyTestBase where TFixture : F1FixtureBase, new() { - protected PropertyEntryTestBase(TFixture fixture) - => Fixture = fixture; - - protected TFixture Fixture { get; } + protected OptimisticConcurrencyRelationalTestBase(TFixture fixture) + : base(fixture) + { } [ConditionalFact] public virtual void Property_entry_original_value_is_set() @@ -37,8 +36,5 @@ public virtual void Property_entry_original_value_is_set() } }); } - - protected F1Context CreateF1Context() - => Fixture.CreateContext(); } } diff --git a/test/EFCore.Specification.Tests/DatabindingTestBase.cs b/test/EFCore.Specification.Tests/DatabindingTestBase.cs index f8beeac7c06..2ca3459dade 100644 --- a/test/EFCore.Specification.Tests/DatabindingTestBase.cs +++ b/test/EFCore.Specification.Tests/DatabindingTestBase.cs @@ -52,6 +52,7 @@ protected void SetupContext(F1Context context) drivers.Add( new Driver { + Id = 43, Name = "Pedro de la Rosa", TeamId = AddedTeam, CarNumber = 13 @@ -59,6 +60,7 @@ protected void SetupContext(F1Context context) drivers.Add( new Driver { + Id = 44, Name = "Kamui Kobayashi", TeamId = AddedTeam, CarNumber = null diff --git a/test/EFCore.Specification.Tests/F1FixtureBase.cs b/test/EFCore.Specification.Tests/F1FixtureBase.cs index c2e95c3cfd1..a53b74e0d75 100644 --- a/test/EFCore.Specification.Tests/F1FixtureBase.cs +++ b/test/EFCore.Specification.Tests/F1FixtureBase.cs @@ -47,6 +47,7 @@ protected virtual void BuildModelExternal(ModelBuilder modelBuilder) modelBuilder.Entity( b => { + b.Property(e => e.Id).ValueGeneratedNever(); b.Property(e => e.EngineSupplierId).IsConcurrencyToken(); b.Property(e => e.Name).IsConcurrencyToken(); b.OwnsOne( @@ -57,7 +58,7 @@ protected virtual void BuildModelExternal(ModelBuilder modelBuilder) }); }); - modelBuilder.Entity(); + modelBuilder.Entity(b => b.HasKey(e => e.Name)); modelBuilder.Entity(); @@ -75,7 +76,10 @@ protected virtual void BuildModelExternal(ModelBuilder modelBuilder) b.HasOne(e => e.Chassis).WithOne(e => e.Team).HasForeignKey(e => e.TeamId); }); + modelBuilder.Entity(b => b.Property(e => e.Id).ValueGeneratedNever()); modelBuilder.Entity(); + + modelBuilder.Entity(b => b.Property(e => e.Id).ValueGeneratedNever()); modelBuilder.Entity() .OwnsOne(s => s.Details); diff --git a/test/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs b/test/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs index 267be8ff135..59a0635b104 100644 --- a/test/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs +++ b/test/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs @@ -48,7 +48,7 @@ public virtual void Nullable_client_side_concurrency_token_can_be_used() c.Database.CreateExecutionStrategy().Execute( c, context => { - using var transaction = context.Database.BeginTransaction(); + using var transaction = BeginTransaction(context.Database); var sponsor = context.Sponsors.Single(s => s.Id == 1); Assert.Null(context.Entry(sponsor).Property(Sponsor.ClientTokenPropertyName).CurrentValue); originalName = sponsor.Name; @@ -134,14 +134,16 @@ public virtual Task Two_concurrency_issues_in_one_to_one_related_entities_can_be return ConcurrencyTestAsync( c => { + var chassis = c.Set().Single(c => c.Name == "MP4-25"); var team = c.Teams.Single(t => t.Id == Team.McLaren); - team.Chassis.Name = "MP4-25b"; + chassis.Name = "MP4-25b"; team.Principal = "Larry David"; }, c => { + var chassis = c.Set().Single(c => c.Name == "MP4-25"); var team = c.Teams.Single(t => t.Id == Team.McLaren); - team.Chassis.Name = "MP4-25c"; + chassis.Name = "MP4-25c"; team.Principal = "Jerry Seinfeld"; }, (c, ex) => @@ -181,14 +183,16 @@ public virtual Task Two_concurrency_issues_in_one_to_many_related_entities_can_b return ConcurrencyTestAsync( c => { + var driver = c.Drivers.Single(d => d.Name == "Jenson Button"); var team = c.Teams.Single(t => t.Id == Team.McLaren); - team.Drivers.Single(d => d.Name == "Jenson Button").Poles = 1; + driver.Poles = 1; team.Principal = "Larry David"; }, c => { + var driver = c.Drivers.Single(d => d.Name == "Jenson Button"); var team = c.Teams.Single(t => t.Id == Team.McLaren); - team.Drivers.Single(d => d.Name == "Jenson Button").Poles = 2; + driver.Poles = 2; team.Principal = "Jerry Seinfeld"; }, (c, ex) => @@ -227,7 +231,7 @@ public virtual Task Concurrency_issue_where_the_FK_is_the_concurrency_token_can_ { return ConcurrencyTestAsync( c => c.Engines.Single(e => e.Name == "056").EngineSupplierId = - c.EngineSuppliers.Single(s => s.Name == "Cosworth").Id, + c.EngineSuppliers.Single(s => s.Name == "Cosworth").Name, c => c.Engines.Single(e => e.Name == "056").EngineSupplier = c.EngineSuppliers.Single(s => s.Name == "Renault"), (c, ex) => @@ -335,7 +339,7 @@ public virtual async Task Adding_the_same_entity_twice_results_in_DbUpdateExcept await c.Database.CreateExecutionStrategy().ExecuteAsync( c, async context => { - using var transaction = context.Database.BeginTransaction(); + using var transaction = BeginTransaction(context.Database); context.Teams.Add( new Team { @@ -456,7 +460,7 @@ public virtual async Task Calling_Reload_on_an_Added_entity_that_is_not_in_datab await c.Database.CreateExecutionStrategy().ExecuteAsync( c, async context => { - using (context.Database.BeginTransaction()) + using (BeginTransaction(context.Database)) { var entry = context.Drivers.Add( new Driver { Name = "Larry David", TeamId = Team.Ferrari }); @@ -505,7 +509,7 @@ private async Task TestReloadGone(EntityState state, bool async) await c.Database.CreateExecutionStrategy().ExecuteAsync( c, async context => { - using (context.Database.BeginTransaction()) + using (BeginTransaction(context.Database)) { var entry = context.Drivers.Add( new Driver @@ -567,7 +571,7 @@ private async Task TestReloadPositive(EntityState state, bool async) await c.Database.CreateExecutionStrategy().ExecuteAsync( c, async context => { - using (context.Database.BeginTransaction()) + using (BeginTransaction(context.Database)) { var larry = context.Drivers.Single(d => d.Name == "Jenson Button"); larry.Name = "Rory Gilmore"; @@ -600,7 +604,7 @@ public virtual async Task Calling_GetDatabaseValues_on_owned_entity_works(bool a await c.Database.CreateExecutionStrategy().ExecuteAsync( c, async context => { - using var transaction = context.Database.BeginTransaction(); + using var transaction = BeginTransaction(context.Database); var titleSponsor = context.Set().Single(t => t.Name == "Vodafone"); var ownerEntry = context.Entry(titleSponsor); @@ -630,7 +634,7 @@ public virtual async Task Calling_Reload_on_owned_entity_works(bool async) await c.Database.CreateExecutionStrategy().ExecuteAsync( c, async context => { - using var transaction = context.Database.BeginTransaction(); + using var transaction = BeginTransaction(context.Database); var titleSponsor = context.Set().Single(t => t.Name == "Vodafone"); var ownerEntry = context.Entry(titleSponsor); @@ -711,7 +715,7 @@ protected virtual async Task ConcurrencyTestAsync( await c.Database.CreateExecutionStrategy().ExecuteAsync( c, async context => { - using var transaction = context.Database.BeginTransaction(); + using var transaction = BeginTransaction(context.Database); clientChange(context); using var innerContext = CreateF1Context(); @@ -740,6 +744,9 @@ await c.Database.CreateExecutionStrategy().ExecuteAsync( }); } + protected virtual IDbContextTransaction BeginTransaction(DatabaseFacade facade) + => facade.BeginTransaction(); + protected virtual void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) { } diff --git a/test/EFCore.Specification.Tests/SerializationTestBase.cs b/test/EFCore.Specification.Tests/SerializationTestBase.cs index 97c38729dfd..242643c1bfd 100644 --- a/test/EFCore.Specification.Tests/SerializationTestBase.cs +++ b/test/EFCore.Specification.Tests/SerializationTestBase.cs @@ -46,7 +46,7 @@ public virtual void Can_round_trip_through_JSON(bool useNewtonsoft, bool ignoreL var teamsMap = ignoreLoops ? null : new Dictionary(); var enginesMap = ignoreLoops ? null : new Dictionary(); - var engineSupplierMap = ignoreLoops ? null : new Dictionary(); + var engineSupplierMap = ignoreLoops ? null : new Dictionary(); foreach (var team in teamsAgain) { @@ -102,19 +102,19 @@ private static void VerifyEngine(F1Context context, Engine engine, IDictionary engineSupplierMap) + IDictionary engineSupplierMap) { - var trackedEngineSupplier = context.EngineSuppliers.Find(engineSupplier.Id); + var trackedEngineSupplier = context.EngineSuppliers.Find(engineSupplier.Name); Assert.Equal(trackedEngineSupplier.Name, engineSupplier.Name); if (engineSupplierMap != null) { - if (engineSupplierMap.TryGetValue(engineSupplier.Id, out var mappedEngineSupplier)) + if (engineSupplierMap.TryGetValue(engineSupplier.Name, out var mappedEngineSupplier)) { Assert.Same(engineSupplier, mappedEngineSupplier); } - engineSupplierMap[engineSupplier.Id] = engineSupplier; + engineSupplierMap[engineSupplier.Name] = engineSupplier; } } diff --git a/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Engine.cs b/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Engine.cs index 1fc41e0e73d..ddd05902f2d 100644 --- a/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Engine.cs +++ b/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/Engine.cs @@ -29,7 +29,7 @@ public Engine(ILazyLoader loader, int id, string name) public Location StorageLocation { get; set; } - public int EngineSupplierId { get; set; } + public string EngineSupplierId { get; set; } public virtual EngineSupplier EngineSupplier { diff --git a/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/EngineSupplier.cs b/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/EngineSupplier.cs index 4ec353020d2..a4a9a2c7fce 100644 --- a/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/EngineSupplier.cs +++ b/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/EngineSupplier.cs @@ -15,14 +15,12 @@ public EngineSupplier() { } - private EngineSupplier(ILazyLoader loader, int id, string name) + private EngineSupplier(ILazyLoader loader, string name) { _loader = loader; - Id = id; Name = name; } - public int Id { get; set; } public string Name { get; set; } public virtual ICollection Engines diff --git a/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/F1Context.cs b/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/F1Context.cs index ea98036eedb..924222d0b8f 100644 --- a/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/F1Context.cs +++ b/test/EFCore.Specification.Tests/TestModels/ConcurrencyModel/F1Context.cs @@ -43,24 +43,28 @@ private static void AddEntities(F1Context context) var engineSuppliers = context.EngineSuppliers.Local; var mercedesEngine = new Engine { + Id = 1, Name = "FO 108X", StorageLocation = new Location { Latitude = 47.64491, Longitude = -122.128101 }, EngineSupplier = engineSuppliers.Single(s => s.Name == "Mercedes") }; var renaultEngine = new Engine { + Id = 2, Name = "RS27-2010", StorageLocation = new Location { Latitude = 47.644199, Longitude = -122.127049 }, EngineSupplier = engineSuppliers.Single(s => s.Name == "Renault") }; var ferrariEngine = new Engine { + Id = 3, Name = "056", StorageLocation = new Location { Latitude = 47.64256, Longitude = -122.130609 }, EngineSupplier = engineSuppliers.Single(s => s.Name == "Ferrari") }; var cosworthEngine = new Engine { + Id = 4, Name = "CA2010", StorageLocation = new Location { Latitude = 47.644851, Longitude = -122.129781 }, EngineSupplier = engineSuppliers.Single(s => s.Name == "Cosworth") @@ -280,6 +284,7 @@ private static void AddEntities(F1Context context) { new Driver { + Id = 1, Name = "Jenson Button", TeamId = Team.McLaren, CarNumber = 1, @@ -292,6 +297,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 2, Name = "Lewis Hamilton", TeamId = Team.McLaren, CarNumber = 2, @@ -304,6 +310,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 3, Name = "Gary Paffett", TeamId = Team.McLaren, CarNumber = null, @@ -316,6 +323,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 4, Name = "Michael Schumacher", TeamId = Team.Mercedes, CarNumber = 3, @@ -328,6 +336,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 5, Name = "Nico Rosberg", TeamId = Team.Mercedes, CarNumber = 4, @@ -340,6 +349,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 6, Name = "Nick Heidfeld", TeamId = Team.Mercedes, CarNumber = null, @@ -352,6 +362,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 7, Name = "Sebastian Vettel", TeamId = Team.RedBull, CarNumber = 5, @@ -364,6 +375,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 8, Name = "Mark Webber", TeamId = Team.RedBull, CarNumber = 6, @@ -376,6 +388,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 9, Name = "Brendon Hartley", TeamId = Team.RedBull, CarNumber = null, @@ -388,6 +401,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 10, Name = "Daniel Ricciardo", TeamId = Team.RedBull, CarNumber = null, @@ -400,6 +414,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 11, Name = "David Coulthard", TeamId = Team.RedBull, CarNumber = null, @@ -412,6 +427,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 12, Name = "Felipe Massa", TeamId = Team.Ferrari, CarNumber = 7, @@ -424,6 +440,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 13, Name = "Fernando Alonso", TeamId = Team.Ferrari, CarNumber = 8, @@ -436,6 +453,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 14, Name = "Giancarlo Fisichella", TeamId = Team.Ferrari, CarNumber = null, @@ -448,6 +466,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 15, Name = "Luca Badoer", TeamId = Team.Ferrari, CarNumber = null, @@ -460,6 +479,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 16, Name = "Marc Gené", TeamId = Team.Ferrari, CarNumber = null, @@ -472,6 +492,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 17, Name = "Rubens Barrichello", TeamId = Team.Williams, CarNumber = 9, @@ -484,6 +505,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 18, Name = "Nico Hülkenberg", TeamId = Team.Williams, CarNumber = 10, @@ -496,6 +518,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 19, Name = "Valtteri Bottas", TeamId = Team.Williams, CarNumber = null, @@ -508,6 +531,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 20, Name = "Robert Kubica", TeamId = Team.Renault, CarNumber = 11, @@ -520,6 +544,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 21, Name = "Vitaly Petrov", TeamId = Team.Renault, CarNumber = 12, @@ -532,6 +557,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 22, Name = "Ho-Pin Tung", TeamId = Team.Renault, CarNumber = null, @@ -544,6 +570,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 23, Name = "Jérôme d'Ambrosio", TeamId = Team.Renault, CarNumber = null, @@ -556,6 +583,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 24, Name = "Jan Charouz", TeamId = Team.Renault, CarNumber = null, @@ -568,6 +596,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 25, Name = "Adrian Sutil", TeamId = Team.ForceIndia, CarNumber = 14, @@ -580,6 +609,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 26, Name = "Vitantonio Liuzzi", TeamId = Team.ForceIndia, CarNumber = 15, @@ -592,6 +622,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 27, Name = "Paul di Resta", TeamId = Team.ForceIndia, CarNumber = null, @@ -604,6 +635,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 28, Name = "Sébastien Buemi", TeamId = Team.ToroRosso, CarNumber = 16, @@ -616,6 +648,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 29, Name = "Jaime Alguersuari", TeamId = Team.ToroRosso, CarNumber = 17, @@ -628,6 +661,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 30, Name = "Brendon Hartley", TeamId = Team.ToroRosso, CarNumber = null, @@ -640,6 +674,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 31, Name = "Daniel Ricciardo", TeamId = Team.ToroRosso, CarNumber = null, @@ -652,6 +687,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 32, Name = "Jarno Trulli", TeamId = Team.Lotus, CarNumber = 18, @@ -664,6 +700,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 33, Name = "Heikki Kovalainen", TeamId = Team.Lotus, CarNumber = 19, @@ -676,6 +713,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 34, Name = "Fairuz Fauzy", TeamId = Team.Lotus, CarNumber = null, @@ -688,6 +726,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 35, Name = "Karun Chandhok", TeamId = Team.Hispania, CarNumber = 20, @@ -700,6 +739,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 36, Name = "Bruno Senna", TeamId = Team.Hispania, CarNumber = 21, @@ -712,6 +752,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 37, Name = "Christian Klien", TeamId = Team.Hispania, CarNumber = null, @@ -724,6 +765,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 38, Name = "Sakon Yamamoto", TeamId = Team.Hispania, CarNumber = null, @@ -736,6 +778,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 39, Name = "Timo Glock", TeamId = Team.Vickers, CarNumber = 24, @@ -748,6 +791,7 @@ private static void AddEntities(F1Context context) }, new Driver { + Id = 40, Name = "Lucas di Grassi", TeamId = Team.Vickers, CarNumber = 25, @@ -760,6 +804,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 41, Name = "Andy Soucek", TeamId = Team.Vickers, CarNumber = null, @@ -772,6 +817,7 @@ private static void AddEntities(F1Context context) }, new TestDriver { + Id = 42, Name = "Luiz Razia", TeamId = Team.Vickers, CarNumber = null, @@ -787,10 +833,27 @@ private static void AddEntities(F1Context context) context.Drivers.Add(driver); } - var shell = new Sponsor { Name = "Shell" }; - var vodafone = new TitleSponsor { Name = "Vodafone", Details = new SponsorDetails { Days = 10, Space = 50m } }; - var bridgestone = new Sponsor { Name = "Bridgestone" }; - var fia = new Sponsor { Name = "FIA" }; + var shell = new Sponsor + { + Id = 1, + Name = "Shell" + }; + var vodafone = new TitleSponsor + { + Id = 2, + Name = "Vodafone", + Details = new SponsorDetails { Days = 10, Space = 50m } + }; + var bridgestone = new Sponsor + { + Id = 3, + Name = "Bridgestone" + }; + var fia = new Sponsor + { + Id = 4, + Name = "FIA" + }; foreach (var sponsor in new List { diff --git a/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs index 4b23d0ead52..6ce20349f97 100644 --- a/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs @@ -11,7 +11,7 @@ // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore { - public class OptimisticConcurrencySqlServerTest : OptimisticConcurrencyTestBase + public class OptimisticConcurrencySqlServerTest : OptimisticConcurrencyRelationalTestBase { public OptimisticConcurrencySqlServerTest(F1SqlServerFixture fixture) : base(fixture) @@ -117,6 +117,31 @@ await c.Database.CreateExecutionStrategy().ExecuteAsync( }); } + public override void Property_entry_original_value_is_set() + { + base.Property_entry_original_value_is_set(); + + AssertSql( + @"SELECT TOP(1) [e].[Id], [e].[EngineSupplierId], [e].[Name], [e].[StorageLocation_Latitude], [e].[StorageLocation_Longitude] +FROM [Engines] AS [e] +ORDER BY [e].[Id]", + // + @"@p1='1' +@p2='Mercedes' (Size = 450) +@p0='FO 108X' (Size = 4000) +@p3='ChangedEngine' (Size = 4000) +@p4='47.64491' (Nullable = true) +@p5='-122.128101' (Nullable = true) + +SET NOCOUNT ON; +UPDATE [Engines] SET [Name] = @p0 +WHERE [Id] = @p1 AND [EngineSupplierId] = @p2 AND [Name] = @p3 AND [StorageLocation_Latitude] = @p4 AND [StorageLocation_Longitude] = @p5; +SELECT @@ROWCOUNT;"); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); } diff --git a/test/EFCore.SqlServer.FunctionalTests/PropertyEntrySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/PropertyEntrySqlServerTest.cs deleted file mode 100644 index 7bcb8e1b01f..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/PropertyEntrySqlServerTest.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Xunit.Abstractions; - -namespace Microsoft.EntityFrameworkCore -{ - public class PropertyEntrySqlServerTest : PropertyEntryTestBase - { - public PropertyEntrySqlServerTest(F1SqlServerFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - } - - public override void Property_entry_original_value_is_set() - { - base.Property_entry_original_value_is_set(); - - AssertSql( - @"SELECT TOP(1) [e].[Id], [e].[EngineSupplierId], [e].[Name], [e].[StorageLocation_Latitude], [e].[StorageLocation_Longitude] -FROM [Engines] AS [e] -ORDER BY [e].[Id]", - // - @"@p1='1' -@p2='1' -@p0='FO 108X' (Size = 4000) -@p3='ChangedEngine' (Size = 4000) -@p4='47.64491' (Nullable = true) -@p5='-122.128101' (Nullable = true) - -SET NOCOUNT ON; -UPDATE [Engines] SET [Name] = @p0 -WHERE [Id] = @p1 AND [EngineSupplierId] = @p2 AND [Name] = @p3 AND [StorageLocation_Latitude] = @p4 AND [StorageLocation_Longitude] = @p5; -SELECT @@ROWCOUNT;"); - } - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - } -} diff --git a/test/EFCore.Sqlite.FunctionalTests/OptimisticConcurrencySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/OptimisticConcurrencySqliteTest.cs index e2cf6b944c2..342e34ac6d6 100644 --- a/test/EFCore.Sqlite.FunctionalTests/OptimisticConcurrencySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/OptimisticConcurrencySqliteTest.cs @@ -7,13 +7,35 @@ namespace Microsoft.EntityFrameworkCore { - public class OptimisticConcurrencySqliteTest : OptimisticConcurrencyTestBase + public class OptimisticConcurrencySqliteTest : OptimisticConcurrencyRelationalTestBase { public OptimisticConcurrencySqliteTest(F1SqliteFixture fixture) : base(fixture) { } + public override void Property_entry_original_value_is_set() + { + base.Property_entry_original_value_is_set(); + + AssertSql( + @"SELECT ""e"".""Id"", ""e"".""EngineSupplierId"", ""e"".""Name"", ""e"".""StorageLocation_Latitude"", ""e"".""StorageLocation_Longitude"" +FROM ""Engines"" AS ""e"" +ORDER BY ""e"".""Id"" +LIMIT 1", + // + @"@p1='1' (DbType = String) +@p2='Mercedes' (Size = 8) +@p0='FO 108X' (Size = 7) +@p3='ChangedEngine' (Size = 13) +@p4='47.64491' (Nullable = true) (DbType = String) +@p5='-122.128101' (Nullable = true) (DbType = String) + +UPDATE ""Engines"" SET ""Name"" = @p0 +WHERE ""Id"" = @p1 AND ""EngineSupplierId"" = @p2 AND ""Name"" = @p3 AND ""StorageLocation_Latitude"" = @p4 AND ""StorageLocation_Longitude"" = @p5; +SELECT changes();"); + } + // Override failing tests because SQLite does not allow store-generated row versions. // Row version behavior could be imitated on SQLite. See Issue #2195 public override Task Simple_concurrency_exception_can_be_resolved_with_store_values() @@ -51,6 +73,9 @@ public override Task Two_concurrency_issues_in_one_to_many_related_entities_can_ public override Task Two_concurrency_issues_in_one_to_one_related_entities_can_be_handled_by_dealing_with_dependent_first() => Task.FromResult(true); + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); } diff --git a/test/EFCore.Sqlite.FunctionalTests/PropertyEntrySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/PropertyEntrySqliteTest.cs deleted file mode 100644 index df8edab8e6a..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/PropertyEntrySqliteTest.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.EntityFrameworkCore -{ - public class PropertyEntrySqliteTest : PropertyEntryTestBase - { - public PropertyEntrySqliteTest(F1SqliteFixture fixture) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - } - - public override void Property_entry_original_value_is_set() - { - base.Property_entry_original_value_is_set(); - - AssertSql( - @"SELECT ""e"".""Id"", ""e"".""EngineSupplierId"", ""e"".""Name"", ""e"".""StorageLocation_Latitude"", ""e"".""StorageLocation_Longitude"" -FROM ""Engines"" AS ""e"" -ORDER BY ""e"".""Id"" -LIMIT 1", - // - @"@p1='1' (DbType = String) -@p2='1' (DbType = String) -@p0='FO 108X' (Size = 7) -@p3='ChangedEngine' (Size = 13) -@p4='47.64491' (Nullable = true) (DbType = String) -@p5='-122.128101' (Nullable = true) (DbType = String) - -UPDATE ""Engines"" SET ""Name"" = @p0 -WHERE ""Id"" = @p1 AND ""EngineSupplierId"" = @p2 AND ""Name"" = @p3 AND ""StorageLocation_Latitude"" = @p4 AND ""StorageLocation_Longitude"" = @p5; -SELECT changes();"); - } - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - } -}