diff --git a/src/WebJobs.Script.WebHost/Diagnostics/DiagnosticEvent.cs b/src/WebJobs.Script.WebHost/Diagnostics/DiagnosticEvent.cs index 0037bd8d03..c73ff8b04b 100644 --- a/src/WebJobs.Script.WebHost/Diagnostics/DiagnosticEvent.cs +++ b/src/WebJobs.Script.WebHost/Diagnostics/DiagnosticEvent.cs @@ -2,14 +2,15 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using System.Threading; -using Microsoft.Azure.Cosmos.Table; +using System.Runtime.Serialization; +using Azure; +using Azure.Data.Tables; using Microsoft.Azure.WebJobs.Script.WebHost.Helpers; using Microsoft.Extensions.Logging; namespace Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics { - public class DiagnosticEvent : TableEntity + public class DiagnosticEvent : ITableEntity { internal const string CurrentEventVersion = "2024-05-01"; @@ -23,6 +24,14 @@ public DiagnosticEvent(string hostId, DateTime timestamp) EventVersion = CurrentEventVersion; } + public string PartitionKey { get; set; } + + public string RowKey { get; set; } + + public DateTimeOffset? Timestamp { get; set; } + + public ETag ETag { get; set; } + public string EventVersion { get; set; } public int HitCount { get; set; } @@ -35,7 +44,7 @@ public DiagnosticEvent(string hostId, DateTime timestamp) public int Level { get; set; } - [IgnoreProperty] + [IgnoreDataMember] public LogLevel LogLevel { get { return (LogLevel)Level; } @@ -44,4 +53,4 @@ public LogLevel LogLevel public string Details { get; set; } } -} +} \ No newline at end of file diff --git a/src/WebJobs.Script.WebHost/Diagnostics/DiagnosticEventTableStorageRepository.cs b/src/WebJobs.Script.WebHost/Diagnostics/DiagnosticEventTableStorageRepository.cs index 2b28c5fdb6..bd3c10932a 100644 --- a/src/WebJobs.Script.WebHost/Diagnostics/DiagnosticEventTableStorageRepository.cs +++ b/src/WebJobs.Script.WebHost/Diagnostics/DiagnosticEventTableStorageRepository.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Azure.Cosmos.Table; +using Azure.Data.Tables; using Microsoft.Azure.WebJobs.Host.Executors; using Microsoft.Azure.WebJobs.Hosting; using Microsoft.Azure.WebJobs.Logging; @@ -33,8 +33,8 @@ public class DiagnosticEventTableStorageRepository : IDiagnosticEventRepository, private readonly object _syncLock = new object(); private ConcurrentDictionary _events = new ConcurrentDictionary(); - private CloudTableClient _tableClient; - private CloudTable _diagnosticEventsTable; + private TableServiceClient _tableClient; + private TableClient _diagnosticEventsTable; private string _hostId; private bool _disposed = false; private bool _purged = false; @@ -55,22 +55,21 @@ public DiagnosticEventTableStorageRepository(IConfiguration configuration, IHost ILogger logger) : this(configuration, hostIdProvider, environment, scriptHost, logger, LogFlushInterval) { } - internal CloudTableClient TableClient + internal TableServiceClient TableClient { get { if (!_environment.IsPlaceholderModeEnabled() && _tableClient == null) { string storageConnectionString = _configuration.GetWebJobsConnectionString(ConnectionStringNames.Storage); - if (!string.IsNullOrEmpty(storageConnectionString) - && CloudStorageAccount.TryParse(storageConnectionString, out CloudStorageAccount account)) + + try { - var tableClientConfig = new TableClientConfiguration(); - _tableClient = new CloudTableClient(account.TableStorageUri, account.Credentials, tableClientConfig); + _tableClient = new TableServiceClient(storageConnectionString); } - else + catch (Exception ex) { - _logger.LogError("Azure Storage connection string is empty or invalid. Unable to write diagnostic events."); + _logger.LogError(ex, "Azure Storage connection string is empty or invalid. Unable to write diagnostic events."); } } @@ -92,7 +91,7 @@ internal string HostId internal ConcurrentDictionary Events => _events; - internal CloudTable GetDiagnosticEventsTable(DateTime? now = null) + internal TableClient GetDiagnosticEventsTable(DateTime? now = null) { if (TableClient != null) { @@ -103,7 +102,7 @@ internal CloudTable GetDiagnosticEventsTable(DateTime? now = null) if (_diagnosticEventsTable == null || currentTableName != _tableName) { _tableName = currentTableName; - _diagnosticEventsTable = TableClient.GetTableReference(_tableName); + _diagnosticEventsTable = TableClient.GetTableClient(_tableName); } } @@ -134,31 +133,27 @@ await Utility.InvokeWithRetriesAsync(async () => foreach (var table in tables) { - var tableRecords = await table.ExecuteQuerySegmentedAsync(new TableQuery(), null); - - // Skip tables that have 0 records - if (tableRecords.Results.Count == 0) - { - continue; - } - - // Delete table if it doesn't have records with EventVersion - var eventVersionDoesNotExists = tableRecords.Results.Any(record => string.IsNullOrEmpty(record.EventVersion) == true); - if (eventVersionDoesNotExists) - { - _logger.LogDebug("Deleting table '{tableName}' as it contains records without an EventVersion.", table.Name); - await table.DeleteIfExistsAsync(); - tableDeleted = true; - continue; - } + var tableQuery = table.QueryAsync(cancellationToken: default); - // If the table does have EventVersion, query if it is an outdated version - var eventVersionOutdated = tableRecords.Results.Any(record => string.Compare(DiagnosticEvent.CurrentEventVersion, record.EventVersion, StringComparison.Ordinal) > 0); - if (eventVersionOutdated) + await foreach (var record in tableQuery) { - _logger.LogDebug("Deleting table '{tableName}' as it contains records with an outdated EventVersion.", table.Name); - await table.DeleteIfExistsAsync(); - tableDeleted = true; + // Delete table if it doesn't have records with EventVersion + if (string.IsNullOrEmpty(record.EventVersion) == true) + { + _logger.LogDebug("Deleting table '{tableName}' as it contains records without an EventVersion.", table.Name); + await table.DeleteAsync(); + tableDeleted = true; + break; + } + + // If the table does have EventVersion, query if it is an outdated version + if (string.Compare(DiagnosticEvent.CurrentEventVersion, record.EventVersion, StringComparison.Ordinal) > 0) + { + _logger.LogDebug("Deleting table '{tableName}' as it contains records with an outdated EventVersion.", table.Name); + await table.DeleteAsync(); + tableDeleted = true; + break; + } } } @@ -177,7 +172,7 @@ await Utility.InvokeWithRetriesAsync(async () => } } - internal virtual async Task FlushLogs(CloudTable table = null) + internal virtual async Task FlushLogs(TableClient table = null) { if (_environment.IsPlaceholderModeEnabled()) { @@ -200,7 +195,7 @@ internal virtual async Task FlushLogs(CloudTable table = null) return; } - bool tableCreated = await TableStorageHelpers.CreateIfNotExistsAsync(table, TableCreationMaxRetryCount); + bool tableCreated = await TableStorageHelpers.CreateIfNotExistsAsync(table, TableClient, TableCreationMaxRetryCount); if (tableCreated) { _logger.LogDebug("Queueing background table purge."); @@ -225,20 +220,20 @@ internal virtual async Task FlushLogs(CloudTable table = null) } } - internal async Task ExecuteBatchAsync(ConcurrentDictionary events, CloudTable table) + internal async Task ExecuteBatchAsync(ConcurrentDictionary events, TableClient table) { try { - var batch = new TableBatchOperation(); + var batch = new List(); foreach (string errorCode in events.Keys) { var diagnosticEvent = events[errorCode]; diagnosticEvent.Message = Sanitizer.Sanitize(diagnosticEvent.Message); diagnosticEvent.Details = Sanitizer.Sanitize(diagnosticEvent.Details); - TableOperation insertOperation = TableOperation.Insert(diagnosticEvent); - batch.Add(insertOperation); + TableTransactionAction insertAction = new TableTransactionAction(TableTransactionActionType.Add, diagnosticEvent); + batch.Add(insertAction); } - await table.ExecuteBatchAsync(batch); + await table.SubmitTransactionAsync(batch); events.Clear(); } catch (Exception ex) diff --git a/src/WebJobs.Script.WebHost/Helpers/TableStorageHelpers.cs b/src/WebJobs.Script.WebHost/Helpers/TableStorageHelpers.cs index 0ba25162b0..2d132778df 100644 --- a/src/WebJobs.Script.WebHost/Helpers/TableStorageHelpers.cs +++ b/src/WebJobs.Script.WebHost/Helpers/TableStorageHelpers.cs @@ -6,7 +6,9 @@ using System.Linq; using System.Net; using System.Threading.Tasks; -using Microsoft.Azure.Cosmos.Table; +using Azure; +using Azure.Data.Tables; +using Azure.Data.Tables.Models; using Microsoft.Extensions.Logging; namespace Microsoft.Azure.WebJobs.Script.WebHost.Helpers @@ -18,19 +20,19 @@ internal static string GetRowKey(DateTime now) return string.Format("{0:D19}-{1}", DateTime.MaxValue.Ticks - now.Ticks, Guid.NewGuid()); } - internal static async Task CreateIfNotExistsAsync(CloudTable table, int tableCreationRetries, int retryDelayMS = 1000) + internal static async Task CreateIfNotExistsAsync(TableClient table, TableServiceClient tableClient, int tableCreationRetries, int retryDelayMS = 1000) { int attempt = 0; do { try { - if (!table.Exists()) + if (!await TableExistAsync(table, tableClient)) { - return await table.CreateIfNotExistsAsync(); + return (await table.CreateIfNotExistsAsync())?.Value is not null; } } - catch (StorageException e) + catch (RequestFailedException rfe) { // Can get conflicts with multiple instances attempting to create // the same table. @@ -38,7 +40,7 @@ internal static async Task CreateIfNotExistsAsync(CloudTable table, int ta // though these should only happen in tests not production, because we only ever // delete OLD tables and we'll never be attempting to recreate a table we just // deleted outside of tests. - if (e.RequestInformation.HttpStatusCode == (int)HttpStatusCode.Conflict && + if ((rfe.Status != (int)HttpStatusCode.Conflict || rfe.ErrorCode == TableErrorCode.TableBeingDeleted) && attempt < tableCreationRetries) { // wait a bit and try again @@ -53,7 +55,7 @@ internal static async Task CreateIfNotExistsAsync(CloudTable table, int ta return false; } - internal static void QueueBackgroundTablePurge(CloudTable currentTable, CloudTableClient tableClient, string tableNamePrefix, ILogger logger, int delaySeconds = 30) + internal static void QueueBackgroundTablePurge(TableClient currentTable, TableServiceClient tableClient, string tableNamePrefix, ILogger logger, int delaySeconds = 30) { var tIgnore = Task.Run(async () => { @@ -74,38 +76,49 @@ internal static void QueueBackgroundTablePurge(CloudTable currentTable, CloudTab }); } - internal static async Task DeleteOldTablesAsync(CloudTable currentTable, CloudTableClient tableClient, string tableNamePrefix, ILogger logger) + internal static async Task DeleteOldTablesAsync(TableClient currentTable, TableServiceClient tableClient, string tableNamePrefix, ILogger logger) { var tablesToDelete = await ListOldTablesAsync(currentTable, tableClient, tableNamePrefix); logger.LogDebug($"Deleting {tablesToDelete.Count()} old tables."); foreach (var table in tablesToDelete) { logger.LogDebug($"Deleting table '{table.Name}'"); - await table.DeleteIfExistsAsync(); + await tableClient.DeleteTableAsync(table.Name); logger.LogDebug($"{table.Name} deleted."); } } - internal static async Task> ListOldTablesAsync(CloudTable currentTable, CloudTableClient tableClient, string tableNamePrefix) + internal static async Task> ListOldTablesAsync(TableClient currentTable, TableServiceClient tableClient, string tableNamePrefix) { var tables = await ListTablesAsync(tableClient, tableNamePrefix); return tables.Where(p => !string.Equals(currentTable.Name, p.Name, StringComparison.OrdinalIgnoreCase)); } - internal static async Task> ListTablesAsync(CloudTableClient tableClient, string tableNamePrefix) + internal static async Task> ListTablesAsync(TableServiceClient tableClient, string tableNamePrefix) { - List tables = new List(); - TableContinuationToken continuationToken = null; + // Azure.Data.Tables doesn't have a direct way to list tables with a prefix so we need to do it manually + var givenValue = tableNamePrefix + "{"; + AsyncPageable tablesQuery = tableClient.QueryAsync(p => p.Name.CompareTo(tableNamePrefix) >= 0 && p.Name.CompareTo(givenValue) <= 0); + var tables = new List(); - do + await foreach (var table in tablesQuery) { - var results = await tableClient.ListTablesSegmentedAsync(tableNamePrefix, continuationToken); - continuationToken = results.ContinuationToken; - tables.AddRange(results.Results); + tables.Add(tableClient.GetTableClient(table.Name)); } - while (continuationToken != null); return tables; } + + internal static async Task TableExistAsync(TableClient table, TableServiceClient tableClient) + { + var query = tableClient.QueryAsync(p => p.Name == table.Name); + + await foreach (var item in query) + { + return true; + } + + return false; + } } -} +} \ No newline at end of file diff --git a/src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj b/src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj index 06ca7631e2..04209dddf3 100644 --- a/src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj +++ b/src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj @@ -63,6 +63,7 @@ + diff --git a/src/WebJobs.Script/runtimeassemblies-net6.json b/src/WebJobs.Script/runtimeassemblies-net6.json index 0785e3c6eb..a6de3b9ef7 100644 --- a/src/WebJobs.Script/runtimeassemblies-net6.json +++ b/src/WebJobs.Script/runtimeassemblies-net6.json @@ -11,6 +11,10 @@ "name": "Azure.Core", "resolutionPolicy": "private" }, + { + "name": "Azure.Data.Tables", + "resolutionPolicy": "private" + }, { "name": "Azure.Identity", "resolutionPolicy": "private" diff --git a/src/WebJobs.Script/runtimeassemblies-net8.json b/src/WebJobs.Script/runtimeassemblies-net8.json index d97d05e00e..11492e196b 100644 --- a/src/WebJobs.Script/runtimeassemblies-net8.json +++ b/src/WebJobs.Script/runtimeassemblies-net8.json @@ -11,6 +11,10 @@ "name": "Azure.Core", "resolutionPolicy": "private" }, + { + "name": "Azure.Data.Tables", + "resolutionPolicy": "private" + }, { "name": "Azure.Identity", "resolutionPolicy": "private" diff --git a/test/WebJobs.Script.Tests.Integration/Diagnostics/DiagnosticEventTableStorageRepositoryTests.cs b/test/WebJobs.Script.Tests.Integration/Diagnostics/DiagnosticEventTableStorageRepositoryTests.cs index 70dbe1d539..81247f85a6 100644 --- a/test/WebJobs.Script.Tests.Integration/Diagnostics/DiagnosticEventTableStorageRepositoryTests.cs +++ b/test/WebJobs.Script.Tests.Integration/Diagnostics/DiagnosticEventTableStorageRepositoryTests.cs @@ -2,6 +2,9 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Host.Executors; @@ -12,13 +15,10 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.WebJobs.Script.Tests; +using Azure.Data.Tables; using Moq; using Moq.Protected; using Xunit; -using System.Linq; -using Microsoft.Azure.Cosmos.Table; -using System.Collections.Generic; -using System.Collections.Concurrent; namespace Microsoft.Azure.WebJobs.Script.Tests.Integration.Diagnostics { @@ -76,7 +76,7 @@ public void WriteDiagnostic_LogsError_WhenHostIdNotSet() IEnvironment testEnvironment = new TestEnvironment(); DiagnosticEventTableStorageRepository repository = - new DiagnosticEventTableStorageRepository(_configuration, null, testEnvironment, _scriptHostMock.Object, _logger); + new DiagnosticEventTableStorageRepository(_configuration, null, testEnvironment, _scriptHostMock.Object, _logger); repository.WriteDiagnosticEvent(DateTime.UtcNow, "eh1", LogLevel.Information, "This is the message", "https://fwlink/", new Exception("exception message")); @@ -175,15 +175,15 @@ public async Task QueueBackgroundDiagnosticsEventsTablePurge_PurgesTables() var tables = await TableStorageHelpers.ListOldTablesAsync(currentTable, repository.TableClient, tablePrefix); foreach (var table in tables) { - await table.DeleteIfExistsAsync(); + await table.DeleteAsync(); } // create 3 old tables for (int i = 0; i < 3; i++) { var tableId = Guid.NewGuid().ToString("N").Substring(0, 5); - var table = repository.TableClient.GetTableReference($"{tablePrefix}Test{tableId}"); - await TableStorageHelpers.CreateIfNotExistsAsync(table, 2); + var table = repository.TableClient.GetTableClient($"{tablePrefix}Test{tableId}"); + await TableStorageHelpers.CreateIfNotExistsAsync(table, repository.TableClient, 2); } // verify tables were created @@ -201,6 +201,51 @@ await TestHelpers.Await(async () => }, timeout: 5000); } + [Fact] + public async Task QueueBackgroundDiagnosticsEventsTablePurge_PurgesOnlyDiagnosticTables() + { + IEnvironment testEnvironment = new TestEnvironment(); + testEnvironment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "0"); + + DiagnosticEventTableStorageRepository repository = + new DiagnosticEventTableStorageRepository(_configuration, _hostIdProvider, testEnvironment, _scriptHostMock.Object, _logger); + + // delete any existing non-current diagnostics events tables + string tablePrefix = DiagnosticEventTableStorageRepository.TableNamePrefix; + var currentTable = repository.GetDiagnosticEventsTable(); + var tables = await TableStorageHelpers.ListOldTablesAsync(currentTable, repository.TableClient, tablePrefix); + foreach (var table in tables) + { + await table.DeleteAsync(); + } + + // create 1 old table + var tableId = Guid.NewGuid().ToString("N").Substring(0, 5); + var oldTable = repository.TableClient.GetTableClient($"{tablePrefix}Test{tableId}"); + await TableStorageHelpers.CreateIfNotExistsAsync(oldTable, repository.TableClient, 2); + + // create a non-diagnostic table + var nonDiagnosticTable = repository.TableClient.GetTableClient("NonDiagnosticTable"); + await TableStorageHelpers.CreateIfNotExistsAsync(nonDiagnosticTable, repository.TableClient, 2); + + // verify tables were created + Assert.True(await TableStorageHelpers.TableExistAsync(oldTable, repository.TableClient)); + Assert.True(await TableStorageHelpers.TableExistAsync(nonDiagnosticTable, repository.TableClient)); + + // queue the background purge + TableStorageHelpers.QueueBackgroundTablePurge(currentTable, repository.TableClient, tablePrefix, NullLogger.Instance, 0); + + // wait for the purge to complete + await TestHelpers.Await(async () => + { + // verify that only the diagnostic table was deleted + var diagnosticTableExist = await TableStorageHelpers.TableExistAsync(oldTable, repository.TableClient); + var nonDiagnosticTableExists = await TableStorageHelpers.TableExistAsync(nonDiagnosticTable, repository.TableClient); + + return !diagnosticTableExist && nonDiagnosticTableExists; + }, timeout: 5000); + } + [Fact] public async Task FlushLogs_LogsErrorAndClearsEvents_WhenTableCreatingFails() { @@ -261,12 +306,12 @@ public async Task FlushLogs_OnPrimaryHost_PurgesPreviousEventVersionTables(strin var tables = await TableStorageHelpers.ListOldTablesAsync(currentTable, repository.TableClient, tablePrefix); foreach (var table in tables) { - await table.DeleteIfExistsAsync(); + await table.DeleteAsync(); } var tableId = Guid.NewGuid().ToString("N").Substring(0, 5); - var testTable = repository.TableClient.GetTableReference($"{tablePrefix}Test{tableId}"); - await TableStorageHelpers.CreateIfNotExistsAsync(testTable, 2); + var testTable = repository.TableClient.GetTableClient($"{tablePrefix}Test{tableId}"); + await TableStorageHelpers.CreateIfNotExistsAsync(testTable, repository.TableClient, 2); // verify table were created tables = await TableStorageHelpers.ListOldTablesAsync(currentTable, repository.TableClient, tablePrefix); @@ -276,8 +321,7 @@ public async Task FlushLogs_OnPrimaryHost_PurgesPreviousEventVersionTables(strin { // add test diagnostic event var diagnosticEvent = CreateDiagnosticEvent(DateTime.UtcNow, "eh1", LogLevel.Information, "This is the message", "https://fwlink/", new Exception("exception message"), testEventVersion); - var insertOperation = TableOperation.Insert(diagnosticEvent); - await testTable.ExecuteAsync(insertOperation); + await testTable.AddEntityAsync(diagnosticEvent); } // Act @@ -307,7 +351,7 @@ public async Task ExecuteBatchAsync_WritesToTableStorage() new DiagnosticEventTableStorageRepository(_configuration, _hostIdProvider, testEnvironment, _scriptHostMock.Object, _logger); var table = repository.GetDiagnosticEventsTable(); - await TableStorageHelpers.CreateIfNotExistsAsync(table, 2); + await TableStorageHelpers.CreateIfNotExistsAsync(table, repository.TableClient, 2); await EmptyTableAsync(table); var dateTime = DateTime.UtcNow; @@ -316,7 +360,7 @@ public async Task ExecuteBatchAsync_WritesToTableStorage() events.TryAdd("EC123", diagnosticEvent); await repository.ExecuteBatchAsync(events, table); - var results = ExecuteQuery(table, new TableQuery()); + var results = ExecuteQuery(repository.TableClient, table); Assert.Equal(results.Count(), 1); } @@ -330,7 +374,7 @@ public async Task FlushLogs_WritesToTableStorage() new DiagnosticEventTableStorageRepository(_configuration, _hostIdProvider, testEnvironment, _scriptHostMock.Object, _logger); var table = repository.GetDiagnosticEventsTable(); - await TableStorageHelpers.CreateIfNotExistsAsync(table, 2); + await TableStorageHelpers.CreateIfNotExistsAsync(table, repository.TableClient, 2); await EmptyTableAsync(table); var dateTime = DateTime.UtcNow; @@ -338,7 +382,7 @@ public async Task FlushLogs_WritesToTableStorage() repository.Events.TryAdd("EC123", diagnosticEvent); await repository.FlushLogs(table); - var results = ExecuteQuery(table, new TableQuery()); + var results = ExecuteQuery(repository.TableClient, table); Assert.Equal(results.Count(), 1); } @@ -352,7 +396,7 @@ public async Task ExecuteBatchAsync_LogsError() new DiagnosticEventTableStorageRepository(_configuration, _hostIdProvider, testEnvironment, _scriptHostMock.Object, _logger); var tableClient = repository.TableClient; - var table = tableClient.GetTableReference("aa"); + var table = tableClient.GetTableClient("aa"); var dateTime = DateTime.UtcNow; var diagnosticEvent = new DiagnosticEvent("hostId", dateTime); @@ -361,7 +405,7 @@ public async Task ExecuteBatchAsync_LogsError() events.TryAdd("EC123", diagnosticEvent); await repository.ExecuteBatchAsync(events, table); - ExecuteQuery(table, new TableQuery()); + ExecuteQuery(tableClient, table); string message = _loggerProvider.GetAllLogMessages()[0].FormattedMessage; Assert.True(message.StartsWith("Unable to write diagnostic events to table storage")); } @@ -382,38 +426,24 @@ private DiagnosticEvent CreateDiagnosticEvent(DateTime timestamp, string errorCo return diagnosticEvent; } - private async Task EmptyTableAsync(CloudTable table) + private async Task EmptyTableAsync(TableClient table) { - var results = ExecuteQuery(table, new TableQuery()); - if (results.Any()) - { - TableBatchOperation batch = new TableBatchOperation(); - foreach (var entity in results) - { - batch.Add(TableOperation.Delete(entity)); + var results = table.QueryAsync(); - if (batch.Count == 1) - { - var result = await table.ExecuteBatchAsync(batch); - batch = new TableBatchOperation(); - } - } - - if (batch.Count > 0) - { - await table.ExecuteBatchAsync(batch); - } + await foreach (var entity in results) + { + await table.DeleteEntityAsync(entity.PartitionKey, entity.RowKey); } } - internal IEnumerable ExecuteQuery(CloudTable table, TableQuery query) + internal IEnumerable ExecuteQuery(TableServiceClient tableClient, TableClient table) { - if (!table.Exists()) + if (!tableClient.Query(p => p.Name == table.Name).Any()) { - return Enumerable.Empty(); + return Enumerable.Empty(); } - return table.ExecuteQuery(query); + return table.Query(); } private class FixedHostIdProvider : IHostIdProvider @@ -431,4 +461,4 @@ public Task GetHostIdAsync(CancellationToken cancellationToken) } } } -} +} \ No newline at end of file diff --git a/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj b/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj index 7272238202..dc92317c5d 100644 --- a/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj +++ b/test/WebJobs.Script.Tests.Integration/WebJobs.Script.Tests.Integration.csproj @@ -30,6 +30,7 @@ + diff --git a/test/WebJobs.Script.Tests/DepsFiles/net6.0/Microsoft.Azure.WebJobs.Script.WebHost.deps.json b/test/WebJobs.Script.Tests/DepsFiles/net6.0/Microsoft.Azure.WebJobs.Script.WebHost.deps.json index 83d8d8a598..3abd5c4611 100644 --- a/test/WebJobs.Script.Tests/DepsFiles/net6.0/Microsoft.Azure.WebJobs.Script.WebHost.deps.json +++ b/test/WebJobs.Script.Tests/DepsFiles/net6.0/Microsoft.Azure.WebJobs.Script.WebHost.deps.json @@ -8,6 +8,7 @@ ".NETCoreApp,Version=v6.0": { "Microsoft.Azure.WebJobs.Script.WebHost/4.34.0": { "dependencies": { + "Azure.Data.Tables": "12.8.3", "Azure.Identity": "1.11.4", "Azure.Security.KeyVault.Secrets": "4.2.0", "Microsoft.ApplicationInsights": "2.22.0", @@ -77,6 +78,18 @@ } } }, + "Azure.Data.Tables/12.8.3": { + "dependencies": { + "Azure.Core": "1.38.0", + "System.Text.Json": "6.0.9" + }, + "runtime": { + "lib/netstandard2.0/Azure.Data.Tables.dll": { + "assemblyVersion": "12.8.3.0", + "fileVersion": "12.800.324.10602" + } + } + }, "Azure.Identity/1.11.4": { "dependencies": { "Azure.Core": "1.38.0", @@ -3111,6 +3124,13 @@ "path": "azure.core/1.38.0", "hashPath": "azure.core.1.38.0.nupkg.sha512" }, + "Azure.Data.Tables/12.8.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-2wAUXLS1ebTnV+qm9qc1z1MZIPC3as2WJt9bEgL08oC1wlT5UhaW78PlYgTld89p/YWCawJycHtBwVx+SWIuLw==", + "path": "azure.data.tables/12.8.3", + "hashPath": "azure.data.tables.12.8.3.nupkg.sha512" + }, "Azure.Identity/1.11.4": { "type": "package", "serviceable": true, diff --git a/test/WebJobs.Script.Tests/DepsFiles/net8.0/Microsoft.Azure.WebJobs.Script.WebHost.deps.json b/test/WebJobs.Script.Tests/DepsFiles/net8.0/Microsoft.Azure.WebJobs.Script.WebHost.deps.json index 20db013f90..478d3ab943 100644 --- a/test/WebJobs.Script.Tests/DepsFiles/net8.0/Microsoft.Azure.WebJobs.Script.WebHost.deps.json +++ b/test/WebJobs.Script.Tests/DepsFiles/net8.0/Microsoft.Azure.WebJobs.Script.WebHost.deps.json @@ -8,6 +8,7 @@ ".NETCoreApp,Version=v8.0": { "Microsoft.Azure.WebJobs.Script.WebHost/4.34.0": { "dependencies": { + "Azure.Data.Tables": "12.8.3", "Azure.Identity": "1.11.4", "Azure.Security.KeyVault.Secrets": "4.2.0", "Microsoft.ApplicationInsights": "2.22.0", @@ -77,6 +78,18 @@ } } }, + "Azure.Data.Tables/12.8.3": { + "dependencies": { + "Azure.Core": "1.38.0", + "System.Text.Json": "6.0.9" + }, + "runtime": { + "lib/netstandard2.0/Azure.Data.Tables.dll": { + "assemblyVersion": "12.8.3.0", + "fileVersion": "12.800.324.10602" + } + } + }, "Azure.Identity/1.11.4": { "dependencies": { "Azure.Core": "1.38.0", @@ -3094,6 +3107,13 @@ "path": "azure.core/1.38.0", "hashPath": "azure.core.1.38.0.nupkg.sha512" }, + "Azure.Data.Tables/12.8.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-2wAUXLS1ebTnV+qm9qc1z1MZIPC3as2WJt9bEgL08oC1wlT5UhaW78PlYgTld89p/YWCawJycHtBwVx+SWIuLw==", + "path": "azure.data.tables/12.8.3", + "hashPath": "azure.data.tables.12.8.3.nupkg.sha512" + }, "Azure.Identity/1.11.4": { "type": "package", "serviceable": true,