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/JetDatabaseCreatorTest.cs

774 lines
30 KiB
C#

// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Data;
using System.Data.Common;
using System.Data.Odbc;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EntityFrameworkCore.Jet.FunctionalTests.TestUtilities;
using EntityFrameworkCore.Jet.Data;
using System.Data.OleDb;
using Microsoft.EntityFrameworkCore.Infrastructure;
using EntityFrameworkCore.Jet.Internal;
using EntityFrameworkCore.Jet.Storage.Internal;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using JetDatabaseCreator = EntityFrameworkCore.Jet.Storage.Internal.JetDatabaseCreator;
#nullable disable
// ReSharper disable InconsistentNaming
namespace EntityFrameworkCore.Jet.FunctionalTests
{
// Tests are split into classes to enable parallel execution
// Some combinations are skipped to reduce run time
[JetCondition(JetCondition.IsNotCI)]
public class JetDatabaseCreatorExistsTest : JetDatabaseCreatorTest
{
[ConditionalTheory]
[InlineData(true, true, false)]
[InlineData(false, false, false)]
[InlineData(true, true, true)]
[InlineData(false, false, true)]
public Task Returns_false_when_database_does_not_exist(bool async, bool ambientTransaction, bool useCanConnect)
{
return Returns_false_when_database_does_not_exist_test(async, ambientTransaction, useCanConnect);
}
[ConditionalTheory]
[InlineData(true, false, false)]
[InlineData(false, true, false)]
[InlineData(true, false, true)]
[InlineData(false, true, true)]
public Task Returns_false_when_database_with_filename_does_not_exist(bool async, bool ambientTransaction, bool useCanConnect)
{
return Returns_false_when_database_does_not_exist_test(async, ambientTransaction, useCanConnect);
}
private static async Task Returns_false_when_database_does_not_exist_test(
bool async, bool ambientTransaction, bool useCanConnect)
{
await using var testDatabase = JetTestStore.Create("NonExisting");
using var context = new BloggingContext(testDatabase);
var creator = GetDatabaseCreator(context);
await context.Database.CreateExecutionStrategy().ExecuteAsync(
async () =>
{
using (CreateTransactionScope(ambientTransaction))
{
if (useCanConnect)
{
Assert.False(async ? await creator.CanConnectAsync() : creator.CanConnect());
}
else
{
Assert.False(async ? await creator.ExistsAsync() : creator.Exists());
}
}
});
Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State);
}
[ConditionalTheory]
[InlineData(true, false, false)]
[InlineData(false, true, false)]
[InlineData(true, false, true)]
[InlineData(false, true, true)]
public Task Returns_true_when_database_exists(bool async, bool ambientTransaction, bool useCanConnect)
{
return Returns_true_when_database_exists_test(async, ambientTransaction, useCanConnect);
}
[ConditionalTheory]
[InlineData(true, true, false)]
[InlineData(false, false, false)]
[InlineData(true, true, true)]
[InlineData(false, false, true)]
public Task Returns_true_when_database_with_filename_exists(bool async, bool ambientTransaction, bool useCanConnect)
{
return Returns_true_when_database_exists_test(async, ambientTransaction, useCanConnect);
}
private static async Task Returns_true_when_database_exists_test(bool async, bool ambientTransaction, bool useCanConnect)
{
await using var testDatabase =
await JetTestStore.CreateInitializedAsync("ExistingBloggingFile");
await using var context = new BloggingContext(testDatabase);
var creator = GetDatabaseCreator(context);
await context.Database.CreateExecutionStrategy().ExecuteAsync(
async () =>
{
using (CreateTransactionScope(ambientTransaction))
{
if (useCanConnect)
{
Assert.True(async ? await creator.CanConnectAsync() : creator.CanConnect());
}
else
{
Assert.True(async ? await creator.ExistsAsync() : creator.Exists());
}
}
});
Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State);
}
}
[JetCondition(JetCondition.IsNotCI)]
public class JetDatabaseCreatorEnsureDeletedTest : JetDatabaseCreatorTest
{
[ConditionalTheory]
[InlineData(true, true, true)]
[InlineData(false, false, true)]
[InlineData(true, false, false)]
[InlineData(false, true, false)]
public Task Deletes_database(bool async, bool open, bool ambientTransaction)
{
return Delete_database_test(async, open, ambientTransaction);
}
[ConditionalTheory]
[InlineData(true, true, false)]
[InlineData(true, false, true)]
[InlineData(false, true, true)]
[InlineData(false, false, false)]
public Task Deletes_database_with_filename(bool async, bool open, bool ambientTransaction)
{
return Delete_database_test(async, open, ambientTransaction);
}
private static async Task Delete_database_test(bool async, bool open, bool ambientTransaction)
{
await using var testDatabase = await JetTestStore.CreateInitializedAsync("EnsureDeleteBloggingFile");
if (!open)
{
testDatabase.CloseConnection();
}
using var context = new BloggingContext(testDatabase);
var creator = GetDatabaseCreator(context);
Assert.True(async ? await creator.ExistsAsync() : creator.Exists());
await GetExecutionStrategy(testDatabase).ExecuteAsync(
async () =>
{
using (CreateTransactionScope(ambientTransaction))
{
if (async)
{
Assert.True(await context.Database.EnsureDeletedAsync());
}
else
{
Assert.True(context.Database.EnsureDeleted());
}
}
});
Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State);
Assert.False(async ? await creator.ExistsAsync() : creator.Exists());
Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State);
}
[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
public Task Noop_when_database_does_not_exist(bool async)
{
return Noop_when_database_does_not_exist_test(async);
}
[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
public Task Noop_when_database_with_filename_does_not_exist(bool async)
{
return Noop_when_database_does_not_exist_test(async);
}
private static async Task Noop_when_database_does_not_exist_test(bool async)
{
await using var testDatabase = JetTestStore.Create("NonExisting");
using var context = new BloggingContext(testDatabase);
var creator = GetDatabaseCreator(context);
Assert.False(async ? await creator.ExistsAsync() : creator.Exists());
if (async)
{
Assert.False(await creator.EnsureDeletedAsync());
}
else
{
Assert.False(creator.EnsureDeleted());
}
Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State);
Assert.False(async ? await creator.ExistsAsync() : creator.Exists());
Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State);
}
}
[JetCondition(JetCondition.IsNotCI)]
public class JetDatabaseCreatorEnsureCreatedTest : JetDatabaseCreatorTest
{
[ConditionalTheory]
[InlineData(true, true)]
[InlineData(false, false)]
public Task Creates_schema_in_existing_database(bool async, bool ambientTransaction)
{
return Creates_schema_in_existing_database_test(async, ambientTransaction);
}
[ConditionalTheory]
[InlineData(true, false)]
[InlineData(false, true)]
public Task Creates_schema_in_existing_database_with_filename(bool async, bool ambientTransaction)
{
return Creates_schema_in_existing_database_test(async, ambientTransaction);
}
private static Task Creates_schema_in_existing_database_test(bool async, bool ambientTransaction)
{
return Creates_physical_database_and_schema_test((true, async, ambientTransaction));
}
[ConditionalTheory]
[InlineData(true, false)]
[InlineData(false, true)]
public Task Creates_physical_database_and_schema(bool async, bool ambientTransaction)
{
return Creates_new_physical_database_and_schema_test(async, ambientTransaction);
}
[ConditionalTheory]
[InlineData(true, true)]
[InlineData(false, false)]
public Task Creates_physical_database_with_filename_and_schema(bool async, bool ambientTransaction)
{
return Creates_new_physical_database_and_schema_test(async, ambientTransaction);
}
private static Task Creates_new_physical_database_and_schema_test(bool async, bool ambientTransaction)
{
return Creates_physical_database_and_schema_test((false, async, ambientTransaction));
}
private static async Task Creates_physical_database_and_schema_test(
(bool CreateDatabase, bool Async, bool ambientTransaction) options)
{
var (createDatabase, async, ambientTransaction) = options;
await using var testDatabase = JetTestStore.Create("EnsureCreatedTestFile");
using var context = new BloggingContext(testDatabase);
if (createDatabase)
{
await testDatabase.InitializeAsync(null, (Func<DbContext>)null);
}
else
{
testDatabase.DeleteDatabase();
}
var creator = GetDatabaseCreator(context);
Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State);
using (CreateTransactionScope(ambientTransaction))
{
if (async)
{
Assert.True(await creator.EnsureCreatedAsync());
}
else
{
Assert.True(creator.EnsureCreated());
}
}
Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State);
if (testDatabase.ConnectionState != ConnectionState.Open)
{
await testDatabase.OpenConnectionAsync();
}
var tables = testDatabase.Query<string>(
"SELECT * FROM `INFORMATION_SCHEMA.TABLES` WHERE TABLE_TYPE = 'BASE TABLE'").ToList();
Assert.Single(tables);
Assert.Equal("Blogs", tables.Single());
/*var dcolumns = testDatabase.ExecuteScalar<>()<DataTable>(
"SELECT * FROM `INFORMATION_SCHEMA.COLUMNS` WHERE TABLE_NAME = 'Blogs' ORDER BY TABLE_NAME, COLUMN_NAME")
.ToArray();*/
//Assert.Single(dcolumns);
//TABLE_NAME + '.' + COLUMN_NAME + ' (' + DATA_TYPE + ')'
/*dcolumns.Select( f => f.)
Assert.Equal(14, columns.Length);
Assert.Equal(
new[]
{
"Blogs.AndChew (varbinary)",
"Blogs.AndRow (timestamp)",
"Blogs.Cheese (nvarchar)",
"Blogs.ErMilan (int)",
"Blogs.Fuse (smallint)",
"Blogs.George (bit)",
"Blogs.Key1 (nvarchar)",
"Blogs.Key2 (varbinary)",
"Blogs.NotFigTime (datetime2)",
"Blogs.On (real)",
"Blogs.OrNothing (float)",
"Blogs.TheGu (uniqueidentifier)",
"Blogs.ToEat (tinyint)",
"Blogs.WayRound (bigint)"
},
columns);*/
}
[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
public Task Noop_when_database_exists_and_has_schema(bool async)
{
return Noop_when_database_exists_and_has_schema_test(async);
}
[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
public Task Noop_when_database_with_filename_exists_and_has_schema(bool async)
{
return Noop_when_database_exists_and_has_schema_test(async);
}
private static async Task Noop_when_database_exists_and_has_schema_test(bool async)
{
await using var testDatabase = await JetTestStore.CreateInitializedAsync("InitializedBloggingFile");
using var context = new BloggingContext(testDatabase);
context.Database.EnsureCreatedResiliently();
if (async)
{
Assert.False(await context.Database.EnsureCreatedResilientlyAsync());
}
else
{
Assert.False(context.Database.EnsureCreatedResiliently());
}
Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State);
}
}
[JetCondition(JetCondition.IsNotCI)]
public class JetDatabaseCreatorHasTablesTest : JetDatabaseCreatorTest
{
[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
public async Task Throws_when_database_does_not_exist(bool async)
{
await using var testDatabase = JetTestStore.GetOrCreate("NonExisting");
var databaseCreator = GetDatabaseCreator(testDatabase);
await databaseCreator.ExecutionStrategy.ExecuteAsync(
databaseCreator,
async creator =>
{
var errorNumber = async
? (await Assert.ThrowsAnyAsync<DbException>(() => creator.HasTablesAsyncBase())).ErrorCode
: Assert.ThrowsAny<DbException>(() => creator.HasTablesBase()).ErrorCode;
Assert.NotEqual(errorNumber, 0);
});
}
[ConditionalTheory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task Returns_false_when_database_exists_but_has_no_tables(bool async, bool ambientTransaction)
{
await using var testDatabase = await JetTestStore.GetOrCreateInitializedAsync("Empty");
var creator = GetDatabaseCreator(testDatabase);
await GetExecutionStrategy(testDatabase).ExecuteAsync(
async () =>
{
using (CreateTransactionScope(ambientTransaction))
{
Assert.False(async ? await creator.HasTablesAsyncBase() : creator.HasTablesBase());
}
});
}
[ConditionalTheory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task Returns_true_when_database_exists_and_has_any_tables(bool async, bool ambientTransaction)
{
await using var testDatabase = await JetTestStore.GetOrCreate("ExistingTables")
.InitializeJetAsync(null, t => new BloggingContext(t), null);
var creator = GetDatabaseCreator(testDatabase);
await GetExecutionStrategy(testDatabase).ExecuteAsync(
async () =>
{
using (CreateTransactionScope(ambientTransaction))
{
Assert.True(async ? await creator.HasTablesAsyncBase() : creator.HasTablesBase());
}
});
}
}
[JetCondition(JetCondition.IsNotCI)]
public class JetDatabaseCreatorDeleteTest : JetDatabaseCreatorTest
{
[ConditionalTheory]
[InlineData(true, true)]
[InlineData(false, false)]
public static async Task Deletes_database(bool async, bool ambientTransaction)
{
await using var testDatabase = await JetTestStore.CreateInitializedAsync("DeleteBlogging");
testDatabase.CloseConnection();
var creator = GetDatabaseCreator(testDatabase);
Assert.True(async ? await creator.ExistsAsync() : creator.Exists());
using (CreateTransactionScope(ambientTransaction))
{
if (async)
{
await creator.DeleteAsync();
}
else
{
creator.Delete();
}
}
Assert.False(async ? await creator.ExistsAsync() : creator.Exists());
}
[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
public async Task Throws_when_database_does_not_exist(bool async)
{
await using var testDatabase = JetTestStore.GetOrCreate("NonExistingBlogging");
var creator = GetDatabaseCreator(testDatabase);
if (async)
{
await Assert.ThrowsAnyAsync<DbException>(() => creator.DeleteAsync());
}
else
{
Assert.ThrowsAny<DbException>(() => creator.Delete());
}
}
}
[JetCondition(JetCondition.IsNotCI)]
public class JetDatabaseCreatorCreateTablesTest : JetDatabaseCreatorTest
{
[ConditionalTheory]
[InlineData(true, true)]
[InlineData(false, false)]
public async Task Creates_schema_in_existing_database_test(bool async, bool ambientTransaction)
{
await using var testDatabase = await JetTestStore.GetOrCreateInitializedAsync("ExistingBlogging" + (async ? "Async" : ""));
using var context = new BloggingContext(testDatabase);
var creator = GetDatabaseCreator(context);
using (CreateTransactionScope(ambientTransaction))
{
if (async)
{
await creator.CreateTablesAsync();
}
else
{
creator.CreateTables();
}
}
if (testDatabase.ConnectionState != ConnectionState.Open)
{
await testDatabase.OpenConnectionAsync();
}
var tables = (await testDatabase.QueryAsync<string>(
"SELECT * FROM `INFORMATION_SCHEMA.TABLES` WHERE TABLE_TYPE = 'BASE TABLE'")).ToList();
Assert.Single(tables);
Assert.Equal("Blogs", tables.Single());
var columns = (await testDatabase.QueryAsync<string>(
"SELECT * FROM `INFORMATION_SCHEMA.COLUMNS` WHERE TABLE_NAME = 'Blogs'")).ToList();
Assert.Equal(14, columns.Count);
Assert.Contains(columns, c => c == "Blogs.Key1");
Assert.Contains(columns, c => c == "Blogs.Key2");
Assert.Contains(columns, c => c == "Blogs.Cheese");
Assert.Contains(columns, c => c == "Blogs.ErMilan");
Assert.Contains(columns, c => c == "Blogs.George");
Assert.Contains(columns, c => c == "Blogs.TheGu");
Assert.Contains(columns, c => c == "Blogs.NotFigTime");
Assert.Contains(columns, c => c == "Blogs.ToEat");
Assert.Contains(columns, c => c == "Blogs.OrNothing");
Assert.Contains(columns, c => c == "Blogs.Fuse");
Assert.Contains(columns, c => c == "Blogs.WayRound");
Assert.Contains(columns, c => c == "Blogs.On");
Assert.Contains(columns, c => c == "Blogs.AndChew");
Assert.Contains(columns, c => c == "Blogs.AndRow");
}
[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
public async Task Throws_if_database_does_not_exist(bool async)
{
await using var testDatabase = JetTestStore.GetOrCreate("NonExisting");
var creator = GetDatabaseCreator(testDatabase);
var exception = async
? await Record.ExceptionAsync(() => creator.CreateTablesAsync())
: Record.Exception(() => creator.CreateTables());
Assert.NotNull(exception);
var isoledbex = exception is OleDbException;
var isodbcex = exception is OdbcException;
int errorNumber = 0;
if (isoledbex)
{
errorNumber = ((OleDbException)exception).ErrorCode;
}
if (isodbcex)
{
errorNumber = ((OdbcException)exception).ErrorCode;
}
Assert.True(isoledbex || isodbcex);
}
[ConditionalFact]
public void GenerateCreateScript_works()
{
using var context = new BloggingContext("Data Source=foo");
var script = context.Database.GenerateCreateScript();
Assert.Equal(
"CREATE TABLE `Blogs` ("
+ _eol
+ " `Key1` varchar(255) NOT NULL,"
+ _eol
+ " `Key2` varbinary(510) NOT NULL,"
+ _eol
+ " `Cheese` longchar NULL,"
+ _eol
+ " `ErMilan` integer NOT NULL,"
+ _eol
+ " `George` smallint NOT NULL,"
+ _eol
+ " `TheGu` uniqueidentifier NOT NULL,"
+ _eol
+ " `NotFigTime` datetime NOT NULL,"
+ _eol
+ " `ToEat` byte NOT NULL,"
+ _eol
+ " `OrNothing` double NOT NULL,"
+ _eol
+ " `Fuse` smallint NOT NULL,"
+ _eol
+ " `WayRound` decimal(20,0) NOT NULL,"
+ _eol
+ " `On` single NOT NULL,"
+ _eol
+ " `AndChew` longbinary NULL,"
+ _eol
+ " `AndRow` varbinary(8) NULL,"
+ _eol
+ " CONSTRAINT `PK_Blogs` PRIMARY KEY (`Key1`, `Key2`)"
+ _eol
+ ");"
+ _eol
+ _eol
+ _eol,
script);
}
private static readonly string _eol = Environment.NewLine;
}
[JetCondition(JetCondition.IsNotCI)]
public class JetDatabaseCreatorCreateTest : JetDatabaseCreatorTest
{
[ConditionalTheory]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Creates_physical_database_but_not_tables(bool async, bool ambientTransaction)
{
await using var testDatabase = JetTestStore.GetOrCreate("CreateTest");
var creator = GetDatabaseCreator(testDatabase);
creator.EnsureDeleted();
await GetExecutionStrategy(testDatabase).ExecuteAsync(
async () =>
{
using (CreateTransactionScope(ambientTransaction))
{
if (async)
{
await creator.CreateAsync();
}
else
{
creator.Create();
}
}
});
Assert.True(creator.Exists());
if (testDatabase.ConnectionState != ConnectionState.Open)
{
await testDatabase.OpenConnectionAsync();
}
Assert.Empty(
(await testDatabase.QueryAsync<string>(
"SELECT * FROM `INFORMATION_SCHEMA.TABLES` WHERE TABLE_TYPE = 'BASE TABLE'")));
}
[ConditionalTheory]
[InlineData(true)]
[InlineData(false)]
public async Task Throws_if_database_already_exists(bool async)
{
await using var testDatabase = await JetTestStore.GetOrCreateInitializedAsync("ExistingBlogging");
var creator = GetDatabaseCreator(testDatabase);
var ex = async
? await Assert.ThrowsAsync<Exception>(() => creator.CreateAsync())
: Assert.Throws<Exception>(() => creator.Create());
//todo:check message
}
}
#pragma warning disable RCS1102 // Make class static.
[JetCondition(JetCondition.IsNotCI)]
public class JetDatabaseCreatorTest
{
protected static IDisposable CreateTransactionScope(bool useTransaction)
=> TestStore.CreateTransactionScope(useTransaction);
protected static TestDatabaseCreator GetDatabaseCreator(JetTestStore testStore)
=> GetDatabaseCreator(testStore.ConnectionString);
protected static TestDatabaseCreator GetDatabaseCreator(string connectionString)
=> GetDatabaseCreator(new BloggingContext(connectionString));
protected static TestDatabaseCreator GetDatabaseCreator(BloggingContext context)
=> (TestDatabaseCreator)context.GetService<IRelationalDatabaseCreator>();
protected static IExecutionStrategy GetExecutionStrategy(JetTestStore testStore)
=> new BloggingContext(testStore).GetService<IExecutionStrategyFactory>().Create();
// ReSharper disable once ClassNeverInstantiated.Local
private class TestJetExecutionStrategyFactory(ExecutionStrategyDependencies dependencies)
: JetExecutionStrategyFactory(dependencies)
{
protected override IExecutionStrategy CreateDefaultStrategy(ExecutionStrategyDependencies dependencies)
{
return new NonRetryingExecutionStrategy(dependencies);
}
}
private static IServiceProvider CreateServiceProvider()
{
return new ServiceCollection()
.AddEntityFrameworkJet()
.AddScoped<IExecutionStrategyFactory, TestJetExecutionStrategyFactory>()
.AddScoped<IRelationalDatabaseCreator, TestDatabaseCreator>()
.BuildServiceProvider();
}
protected class BloggingContext(string connectionString) : DbContext
{
public BloggingContext(JetTestStore testStore)
: this(testStore.ConnectionString)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseJet(connectionString, TestEnvironment.DataAccessProviderFactory, b => b.ApplyConfiguration())
.UseInternalServiceProvider(CreateServiceProvider());
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>(
b =>
{
b.HasKey(
e => new { e.Key1, e.Key2 });
b.Property(e => e.AndRow).IsConcurrencyToken().ValueGeneratedOnAddOrUpdate();
});
}
public DbSet<Blog> Blogs { get; set; }
}
public class Blog
{
public string Key1 { get; set; }
public byte[] Key2 { get; set; }
public string Cheese { get; set; }
public int ErMilan { get; set; }
public bool George { get; set; }
public Guid TheGu { get; set; }
public DateTime NotFigTime { get; set; }
public byte ToEat { get; set; }
public double OrNothing { get; set; }
public short Fuse { get; set; }
public long WayRound { get; set; }
public float On { get; set; }
public byte[] AndChew { get; set; }
public byte[] AndRow { get; set; }
}
public class TestDatabaseCreator(
RelationalDatabaseCreatorDependencies dependencies,
IJetRelationalConnection connection,
IRawSqlCommandBuilder rawSqlCommandBuilder)
: JetDatabaseCreator(dependencies, connection, rawSqlCommandBuilder)
{
public bool HasTablesBase()
=> HasTables();
public Task<bool> HasTablesAsyncBase(CancellationToken cancellationToken = default)
=> HasTablesAsync(cancellationToken);
public IExecutionStrategy ExecutionStrategy
=> Dependencies.ExecutionStrategy;
}
}
}