// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Data.Common; using System.Threading; using System.Threading.Tasks; using EntityFrameworkCore.Jet.FunctionalTests.TestUtilities; using EntityFrameworkCore.Jet.Infrastructure; using EntityFrameworkCore.Jet.Storage.Internal; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace EntityFrameworkCore.Jet.FunctionalTests; public abstract class SaveChangesInterceptionJetTestBase : SaveChangesInterceptionTestBase { protected SaveChangesInterceptionJetTestBase(InterceptionJetFixtureBase fixture) : base(fixture) { } [ConditionalTheory] [InlineData(false, false, false)] [InlineData(true, false, false)] [InlineData(false, true, false)] [InlineData(true, true, false)] [InlineData(false, false, true)] [InlineData(true, false, true)] [InlineData(false, true, true)] [InlineData(true, true, true)] public virtual async Task Intercept_concurrency_with_relational_specific_data(bool async, bool inject, bool noAcceptChanges) { var saveChangesInterceptor = new RelationalConcurrencySaveChangesInterceptor(); var commandInterceptor = new TestCommandInterceptor(); var context = CreateContext(saveChangesInterceptor, commandInterceptor); using var _ = context; using var transaction = context.Database.BeginTransaction(); var entry = context.Entry(new Singularity { Id = 35, Type = "Red Dwarf" }); entry.State = EntityState.Modified; using var listener = Fixture.SubscribeToDiagnosticListener(context.ContextId); Exception thrown = null; try { var __ = noAcceptChanges ? async ? await context.SaveChangesAsync() : context.SaveChanges() : async ? await context.SaveChangesAsync(acceptAllChangesOnSuccess: false) : context.SaveChanges(acceptAllChangesOnSuccess: false); } catch (Exception e) { thrown = e; } Assert.Equal(async, saveChangesInterceptor.AsyncCalled); Assert.NotEqual(async, saveChangesInterceptor.SyncCalled); Assert.NotEqual(saveChangesInterceptor.AsyncCalled, saveChangesInterceptor.SyncCalled); Assert.False(saveChangesInterceptor.FailedCalled); Assert.Same(context, saveChangesInterceptor.Context); Assert.Same(thrown, saveChangesInterceptor.Exception); Assert.True(saveChangesInterceptor.ConcurrencyExceptionCalled); Assert.Equal(1, saveChangesInterceptor.Entries.Count); Assert.Same(entry.Entity, saveChangesInterceptor.Entries[0].Entity); Assert.Same(commandInterceptor.Connection, saveChangesInterceptor.Connection); Assert.Same(commandInterceptor.Command, saveChangesInterceptor.Command); Assert.Same(commandInterceptor.DataReader, saveChangesInterceptor.DataReader); Assert.Equal(commandInterceptor.ConnectionId, saveChangesInterceptor.ConnectionId); Assert.Equal(commandInterceptor.CommandId, saveChangesInterceptor.CommandId); } protected class RelationalConcurrencySaveChangesInterceptor : SaveChangesInterceptorBase { public DbConnection Connection { get; set; } public DbCommand Command { get; set; } public DbDataReader DataReader { get; set; } public Guid CommandId { get; set; } public Guid ConnectionId { get; set; } public override InterceptionResult ThrowingConcurrencyException(ConcurrencyExceptionEventData eventData, InterceptionResult result) { RecordEventData((RelationalConcurrencyExceptionEventData)eventData); return base.ThrowingConcurrencyException(eventData, result); } public override ValueTask ThrowingConcurrencyExceptionAsync( ConcurrencyExceptionEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) { RecordEventData((RelationalConcurrencyExceptionEventData)eventData); return base.ThrowingConcurrencyExceptionAsync(eventData, result, cancellationToken); } private void RecordEventData(RelationalConcurrencyExceptionEventData eventData) { Assert.NotNull(eventData.Connection); Assert.NotNull(eventData.Command); Assert.NotNull(eventData.DataReader); DataReader = eventData.DataReader; Command = eventData.Command; Connection = eventData.Connection; ConnectionId = eventData.ConnectionId; CommandId = eventData.CommandId; } } protected class TestCommandInterceptor : IDbCommandInterceptor { public DbConnection Connection { get; set; } public DbCommand Command { get; set; } public DbDataReader DataReader { get; set; } public Guid CommandId { get; set; } public Guid ConnectionId { get; set; } public virtual DbDataReader ReaderExecuted( DbCommand command, CommandExecutedEventData eventData, DbDataReader result) { RecordEventData(command, eventData, result); return result; } public virtual ValueTask ReaderExecutedAsync( DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default) { RecordEventData(command, eventData, result); return ValueTask.FromResult(result); } private void RecordEventData(DbCommand command, CommandExecutedEventData eventData, DbDataReader result) { DataReader = result; Command = command; Connection = command.Connection; ConnectionId = eventData.ConnectionId; CommandId = eventData.CommandId; } } public abstract class InterceptionJetFixtureBase : InterceptionFixtureBase { protected override ITestStoreFactory TestStoreFactory => JetTestStoreFactory.Instance; protected override IServiceCollection InjectInterceptors( IServiceCollection serviceCollection, IEnumerable injectedInterceptors) => base.InjectInterceptors(serviceCollection.AddEntityFrameworkJet(), injectedInterceptors); public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) { new JetDbContextOptionsBuilder(base.AddOptions(builder)) .ExecutionStrategy(d => new JetExecutionStrategy(d)); return builder; } } public class SaveChangesInterceptionJetTest : SaveChangesInterceptionJetTestBase, IClassFixture { public SaveChangesInterceptionJetTest(InterceptionJetFixture fixture) : base(fixture) { } public class InterceptionJetFixture : InterceptionJetFixtureBase { protected override string StoreName => "SaveChangesInterception"; protected override bool ShouldSubscribeToDiagnosticListener => false; } } public class SaveChangesInterceptionWithDiagnosticsJetTest : SaveChangesInterceptionJetTestBase, IClassFixture { public SaveChangesInterceptionWithDiagnosticsJetTest(InterceptionJetFixture fixture) : base(fixture) { } public class InterceptionJetFixture : InterceptionJetFixtureBase { protected override string StoreName => "SaveChangesInterceptionWithDiagnostics"; protected override bool ShouldSubscribeToDiagnosticListener => true; } } }