Skip to content

Commit

Permalink
Enable OptimisticConcurrencyTest on Cosmos
Browse files Browse the repository at this point in the history
Fixes #22165
  • Loading branch information
AndriySvyryd committed Nov 17, 2020
1 parent 3f26eed commit 9552d84
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 28 deletions.
2 changes: 1 addition & 1 deletion src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
54 changes: 54 additions & 0 deletions test/EFCore.Cosmos.FunctionalTests/F1CosmosFixture.cs
Original file line number Diff line number Diff line change
@@ -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<Engine>(
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<Chassis>().Property<string>("Version").IsETagConcurrency();
modelBuilder.Entity<Driver>().Property<string>("Version").IsETagConcurrency();
modelBuilder.Entity<Team>().Property<string>("Version").IsETagConcurrency();

modelBuilder.Entity<Sponsor>(
eb =>
{
eb.Property<string>("Version").IsETagConcurrency();
eb.Property<int?>(Sponsor.ClientTokenPropertyName).IsConcurrencyToken(false);
});
modelBuilder.Entity<TitleSponsor>()
.OwnsOne(
s => s.Details, eb =>
{
eb.Property<string>("Version").IsETagConcurrency();
eb.Property<int?>(Sponsor.ClientTokenPropertyName);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -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<F1CosmosFixture>
{
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;
}
}
}
2 changes: 2 additions & 0 deletions test/EFCore.Specification.Tests/DatabindingTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,15 @@ protected void SetupContext(F1Context context)
drivers.Add(
new Driver
{
Id = 43,
Name = "Pedro de la Rosa",
TeamId = AddedTeam,
CarNumber = 13
});
drivers.Add(
new Driver
{
Id = 44,
Name = "Kamui Kobayashi",
TeamId = AddedTeam,
CarNumber = null
Expand Down
6 changes: 5 additions & 1 deletion test/EFCore.Specification.Tests/F1FixtureBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ protected virtual void BuildModelExternal(ModelBuilder modelBuilder)
modelBuilder.Entity<Engine>(
b =>
{
b.Property(e => e.Id).ValueGeneratedNever();
b.Property(e => e.EngineSupplierId).IsConcurrencyToken();
b.Property(e => e.Name).IsConcurrencyToken();
b.OwnsOne(
Expand All @@ -57,7 +58,7 @@ protected virtual void BuildModelExternal(ModelBuilder modelBuilder)
});
});

modelBuilder.Entity<EngineSupplier>();
modelBuilder.Entity<EngineSupplier>(b => b.HasKey(e => e.Name));

modelBuilder.Entity<Gearbox>();

Expand All @@ -75,7 +76,10 @@ protected virtual void BuildModelExternal(ModelBuilder modelBuilder)
b.HasOne(e => e.Chassis).WithOne(e => e.Team).HasForeignKey<Chassis>(e => e.TeamId);
});

modelBuilder.Entity<Driver>(b => b.Property(e => e.Id).ValueGeneratedNever());
modelBuilder.Entity<TestDriver>();

modelBuilder.Entity<Sponsor>(b => b.Property(e => e.Id).ValueGeneratedNever());
modelBuilder.Entity<TitleSponsor>()
.OwnsOne(s => s.Details);

Expand Down
33 changes: 20 additions & 13 deletions test/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int?>(Sponsor.ClientTokenPropertyName).CurrentValue);
originalName = sponsor.Name;
Expand Down Expand Up @@ -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<Chassis>().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<Chassis>().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) =>
Expand Down Expand Up @@ -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) =>
Expand Down Expand Up @@ -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) =>
Expand Down Expand Up @@ -339,7 +343,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
{
Expand Down Expand Up @@ -460,7 +464,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 });
Expand Down Expand Up @@ -509,7 +513,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
Expand Down Expand Up @@ -571,7 +575,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";
Expand Down Expand Up @@ -604,7 +608,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<TitleSponsor>().Single(t => t.Name == "Vodafone");
var ownerEntry = context.Entry(titleSponsor);
Expand Down Expand Up @@ -634,7 +638,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<TitleSponsor>().Single(t => t.Name == "Vodafone");
var ownerEntry = context.Entry(titleSponsor);
Expand Down Expand Up @@ -715,7 +719,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();
Expand Down Expand Up @@ -744,6 +748,9 @@ await c.Database.CreateExecutionStrategy().ExecuteAsync(
});
}
protected virtual IDbContextTransaction BeginTransaction(DatabaseFacade facade)
=> facade.BeginTransaction();
protected virtual void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
{
}
Expand Down
10 changes: 5 additions & 5 deletions test/EFCore.Specification.Tests/SerializationTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public virtual void Can_round_trip_through_JSON(bool useNewtonsoft, bool ignoreL

var teamsMap = ignoreLoops ? null : new Dictionary<int, Team>();
var enginesMap = ignoreLoops ? null : new Dictionary<int, Engine>();
var engineSupplierMap = ignoreLoops ? null : new Dictionary<int, EngineSupplier>();
var engineSupplierMap = ignoreLoops ? null : new Dictionary<string, EngineSupplier>();

foreach (var team in teamsAgain)
{
Expand Down Expand Up @@ -102,19 +102,19 @@ private static void VerifyEngine(F1Context context, Engine engine, IDictionary<i
private static void VerifyEngineSupplier(
F1Context context,
EngineSupplier engineSupplier,
IDictionary<int, EngineSupplier> engineSupplierMap)
IDictionary<string, EngineSupplier> 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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Engine> Engines
Expand Down
Loading

0 comments on commit 9552d84

Please sign in to comment.