You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
EntityFrameworkCore.Jet/test/EFCore.Jet.FunctionalTests/OptimisticConcurrencyJetTes...

382 lines
16 KiB
C#

// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel;
using Xunit;
#nullable disable
// ReSharper disable InconsistentNaming
namespace EntityFrameworkCore.Jet.FunctionalTests
{
public class OptimisticConcurrencyULongJetTest(F1ULongJetFixture fixture)
: OptimisticConcurrencyJetTestBase<F1ULongJetFixture, ulong>(fixture);
public class OptimisticConcurrencyJetTest(F1JetFixture fixture)
: OptimisticConcurrencyJetTestBase<F1JetFixture, byte[]>(fixture);
public abstract class OptimisticConcurrencyJetTestBase<TFixture, TRowVersion>(TFixture fixture)
: OptimisticConcurrencyRelationalTestBase<TFixture, TRowVersion>(fixture)
where TFixture : F1RelationalFixture<TRowVersion>, new()
{
protected enum Mapping
{
Tph,
Tpt,
Tpc
}
protected async Task Row_version_with_owned_types<TEntity, TVersion>(bool updateOwnedFirst, Mapping mapping, string propertyName)
where TEntity : class, ISuperFan
{
await using var c = CreateF1Context();
await c.Database.CreateExecutionStrategy().ExecuteAsync(
c, async context =>
{
var synthesizedPropertyName = $"_TableSharingConcurrencyTokenConvention_{propertyName}";
await using var transaction = BeginTransaction(context.Database);
var fan = context.Set<TEntity>().Single(e => e.Name == "Alice");
var fanEntry = c.Entry(fan);
var swagEntry = fanEntry.Reference(s => s.Swag).TargetEntry!;
var fanVersion1 = fanEntry.Property<TVersion>(propertyName).CurrentValue;
var swagVersion1 = default(TVersion);
if (mapping == Mapping.Tph) // Issue #29750
{
swagVersion1 = swagEntry.Property<TVersion>(synthesizedPropertyName).CurrentValue;
Assert.Equal(fanVersion1, swagVersion1);
}
await using var innerContext = CreateF1Context();
UseTransaction(innerContext.Database, transaction);
var fanInner = innerContext.Set<TEntity>().Single(e => e.Name == "Alice");
if (updateOwnedFirst)
{
fan.Swag.Stuff += "+";
fanInner.Swag.Stuff += "-";
}
else
{
fanInner.Name += "-";
fan.Name += "+";
}
await innerContext.SaveChangesAsync();
if (updateOwnedFirst && mapping == Mapping.Tpt) // Issue #22060
{
await context.SaveChangesAsync();
return;
}
await Assert.ThrowsAnyAsync<DbUpdateConcurrencyException>(() => context.SaveChangesAsync());
await fanEntry.ReloadAsync();
await swagEntry.ReloadAsync();
await context.SaveChangesAsync();
var fanVersion2 = fanEntry.Property<TVersion>(propertyName).CurrentValue;
Assert.NotEqual(fanVersion1, fanVersion2);
var swagVersion2 = default(TVersion);
if (mapping == Mapping.Tph) // Issue #29750
{
swagVersion2 = swagEntry.Property<TVersion>(synthesizedPropertyName).CurrentValue;
Assert.Equal(fanVersion2, swagVersion2);
Assert.NotEqual(swagVersion1, swagVersion2);
}
await innerContext.Entry(fanInner).ReloadAsync();
await innerContext.Entry(fanInner.Swag).ReloadAsync();
if (updateOwnedFirst)
{
fanInner.Name += "-";
fan.Name += "+";
}
else
{
fan.Swag.Stuff += "+";
fanInner.Swag.Stuff += "-";
}
await innerContext.SaveChangesAsync();
if (!updateOwnedFirst && mapping == Mapping.Tpt) // Issue #22060
{
await context.SaveChangesAsync();
return;
}
await Assert.ThrowsAnyAsync<DbUpdateConcurrencyException>(() => context.SaveChangesAsync());
await fanEntry.ReloadAsync();
await swagEntry.ReloadAsync();
await context.SaveChangesAsync();
var fanVersion3 = fanEntry.Property<TVersion>(propertyName).CurrentValue;
Assert.NotEqual(fanVersion2, fanVersion3);
if (mapping == Mapping.Tph) // Issue #29750
{
var swagVersion3 = swagEntry.Property<TVersion>(synthesizedPropertyName).CurrentValue;
Assert.Equal(fanVersion3, swagVersion3);
Assert.NotEqual(swagVersion2, swagVersion3);
}
});
}
protected async Task Row_version_with_table_splitting<TEntity, TCity, TVersion>(
bool updateDependentFirst,
Mapping mapping,
string propertyName)
where TEntity : class, IStreetCircuit<TCity>
where TCity : class, ICity
{
await using var c = CreateF1Context();
await c.Database.CreateExecutionStrategy().ExecuteAsync(
c, async context =>
{
var synthesizedPropertyName = $"_TableSharingConcurrencyTokenConvention_{propertyName}";
await using var transaction = BeginTransaction(context.Database);
var circuit = context.Set<TEntity>().Include(e => e.City).Single(e => e.Name == "Monaco");
var circuitEntry = c.Entry(circuit);
var cityEntry = circuitEntry.Reference(s => s.City).TargetEntry!;
var circuitVersion1 = circuitEntry.Property<TVersion>(propertyName).CurrentValue;
var cityVersion1 = default(TVersion);
if (mapping == Mapping.Tph) // Issue #29750
{
cityVersion1 = cityEntry.Property<TVersion>(synthesizedPropertyName).CurrentValue;
Assert.Equal(circuitVersion1, cityVersion1);
}
await using var innerContext = CreateF1Context();
UseTransaction(innerContext.Database, transaction);
var circuitInner = innerContext.Set<TEntity>().Include(e => e.City).Single(e => e.Name == "Monaco");
if (updateDependentFirst)
{
circuit.City.Name += "+";
circuitInner.City.Name += "-";
}
else
{
circuit.Name += "+";
circuitInner.Name += "-";
}
await innerContext.SaveChangesAsync();
if (updateDependentFirst && mapping == Mapping.Tpt) // Issue #22060
{
await context.SaveChangesAsync();
return;
}
await Assert.ThrowsAnyAsync<DbUpdateConcurrencyException>(() => context.SaveChangesAsync());
await circuitEntry.ReloadAsync();
await cityEntry.ReloadAsync();
await context.SaveChangesAsync();
var circuitVersion2 = circuitEntry.Property<TVersion>(propertyName).CurrentValue;
Assert.NotEqual(circuitVersion1, circuitVersion2);
var cityVersion2 = default(TVersion);
if (mapping == Mapping.Tph) // Issue #29750
{
cityVersion2 = cityEntry.Property<TVersion>(synthesizedPropertyName).CurrentValue;
Assert.Equal(circuitVersion2, cityVersion2);
Assert.NotEqual(cityVersion1, cityVersion2);
}
await innerContext.Entry(circuitInner).ReloadAsync();
await innerContext.Entry(circuitInner.City).ReloadAsync();
if (updateDependentFirst)
{
circuit.Name += "+";
circuitInner.Name += "-";
}
else
{
circuit.City.Name += "+";
circuitInner.City.Name += "-";
}
await innerContext.SaveChangesAsync();
if (!updateDependentFirst && mapping == Mapping.Tpt) // Issue #22060
{
await context.SaveChangesAsync();
return;
}
await Assert.ThrowsAnyAsync<DbUpdateConcurrencyException>(() => context.SaveChangesAsync());
await circuitEntry.ReloadAsync();
await cityEntry.ReloadAsync();
await context.SaveChangesAsync();
var circuitVersion3 = circuitEntry.Property<TVersion>(propertyName).CurrentValue;
Assert.NotEqual(circuitVersion2, circuitVersion3);
if (mapping == Mapping.Tph) // Issue #29750
{
var cityVersion3 = cityEntry.Property<TVersion>(synthesizedPropertyName).CurrentValue;
Assert.Equal(circuitVersion3, cityVersion3);
Assert.NotEqual(cityVersion2, cityVersion3);
}
});
}
[ConditionalFact]
public async Task Modifying_concurrency_token_only_is_noop()
{
using var c = CreateF1Context();
await c.Database.CreateExecutionStrategy().ExecuteAsync(
c, async context =>
{
using var transaction = context.Database.BeginTransaction();
var driver = context.Drivers.Single(d => d.CarNumber == 1);
driver.Podiums = StorePodiums;
var firstVersion = context.Entry(driver).Property<TRowVersion>("Version").CurrentValue;
await context.SaveChangesAsync();
using var innerContext = CreateF1Context();
innerContext.Database.UseTransaction(transaction.GetDbTransaction());
driver = innerContext.Drivers.Single(d => d.CarNumber == 1);
Assert.NotEqual(firstVersion, innerContext.Entry(driver).Property<TRowVersion>("Version").CurrentValue);
Assert.Equal(StorePodiums, driver.Podiums);
var secondVersion = innerContext.Entry(driver).Property<TRowVersion>("Version").CurrentValue;
innerContext.Entry(driver).Property<TRowVersion>("Version").CurrentValue = firstVersion;
await innerContext.SaveChangesAsync();
using var validationContext = CreateF1Context();
validationContext.Database.UseTransaction(transaction.GetDbTransaction());
driver = validationContext.Drivers.Single(d => d.CarNumber == 1);
Assert.Equal(secondVersion, validationContext.Entry(driver).Property<TRowVersion>("Version").CurrentValue);
Assert.Equal(StorePodiums, driver.Podiums);
});
}
[ConditionalFact]
public async Task Database_concurrency_token_value_is_updated_for_all_sharing_entities()
{
using var c = CreateF1Context();
await c.Database.CreateExecutionStrategy().ExecuteAsync(
c, async context =>
{
using var transaction = context.Database.BeginTransaction();
var sponsor = context.Set<TitleSponsor>().Single();
var sponsorEntry = c.Entry(sponsor);
var detailsEntry = sponsorEntry.Reference(s => s.Details).TargetEntry;
var sponsorVersion = sponsorEntry.Property<TRowVersion>("Version").CurrentValue;
var detailsVersion = detailsEntry.Property<TRowVersion>("Version").CurrentValue;
Assert.Null(sponsorEntry.Property<int?>(Sponsor.ClientTokenPropertyName).CurrentValue);
sponsorEntry.Property<int?>(Sponsor.ClientTokenPropertyName).CurrentValue = 1;
sponsor.Name = "Telecom";
Assert.Equal(sponsorVersion, detailsVersion);
await context.SaveChangesAsync();
var newSponsorVersion = sponsorEntry.Property<TRowVersion>("Version").CurrentValue;
var newDetailsVersion = detailsEntry.Property<TRowVersion>("Version").CurrentValue;
Assert.Equal(newSponsorVersion, newDetailsVersion);
Assert.NotEqual(sponsorVersion, newSponsorVersion);
Assert.Equal(1, sponsorEntry.Property<int?>(Sponsor.ClientTokenPropertyName).CurrentValue);
Assert.Equal(1, detailsEntry.Property<int?>(Sponsor.ClientTokenPropertyName).CurrentValue);
});
}
[ConditionalFact]
public async Task Original_concurrency_token_value_is_used_when_replacing_owned_instance()
{
using var c = CreateF1Context();
await c.Database.CreateExecutionStrategy().ExecuteAsync(
c, async context =>
{
using var transaction = context.Database.BeginTransaction();
var sponsor = context.Set<TitleSponsor>().Single();
var sponsorEntry = c.Entry(sponsor);
var sponsorVersion = sponsorEntry.Property<TRowVersion>("Version").CurrentValue;
Assert.Null(sponsorEntry.Property<int?>(Sponsor.ClientTokenPropertyName).CurrentValue);
sponsorEntry.Property<int?>(Sponsor.ClientTokenPropertyName).CurrentValue = 1;
sponsor.Details = new SponsorDetails { Days = 11, Space = 51m };
context.ChangeTracker.DetectChanges();
var detailsEntry = sponsorEntry.Reference(s => s.Details).TargetEntry;
detailsEntry.Property<int?>(Sponsor.ClientTokenPropertyName).CurrentValue = 1;
await context.SaveChangesAsync();
var newSponsorVersion = sponsorEntry.Property<TRowVersion>("Version").CurrentValue;
var newDetailsVersion = detailsEntry.Property<TRowVersion>("Version").CurrentValue;
Assert.Equal(newSponsorVersion, newDetailsVersion);
Assert.NotEqual(sponsorVersion, newSponsorVersion);
Assert.Equal(1, sponsorEntry.Property<int?>(Sponsor.ClientTokenPropertyName).CurrentValue);
Assert.Equal(1, detailsEntry.Property<int?>(Sponsor.ClientTokenPropertyName).CurrentValue);
});
}
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`
""",
//
"""
@p0='FO 108X' (Size = 255)
@p1='1'
@p2='Mercedes' (Size = 255)
@p3='ChangedEngine' (Size = 255)
@p4='47.64491' (Nullable = true)
@p5='-122.128101' (Nullable = true)
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());
}
}