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.
382 lines
16 KiB
C#
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());
|
|
}
|
|
}
|