diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 7a1712b7..499b69b6 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,4 +1,5 @@
+
net9.0
https://github.com/arcenox-co/TickerQ
@@ -8,7 +9,7 @@
icon.jpg
true
9.1.0
- [9.0.0,10.0.0)
+ [10.0.0,11.0.0)
default
@@ -17,5 +18,4 @@
-
diff --git a/src/TickerQ.EntityFrameworkCore/Infrastructure/BasePersistenceProvider.cs b/src/TickerQ.EntityFrameworkCore/Infrastructure/BasePersistenceProvider.cs
index b4d0fd49..f81ecb53 100644
--- a/src/TickerQ.EntityFrameworkCore/Infrastructure/BasePersistenceProvider.cs
+++ b/src/TickerQ.EntityFrameworkCore/Infrastructure/BasePersistenceProvider.cs
@@ -103,9 +103,10 @@ public async Task ReleaseAcquiredTimeTickers(Guid[] timeTickerIds, CancellationT
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);;
var now = _clock.UtcNow;
- var baseQuery = timeTickerIds.Length == 0
+ var idList = timeTickerIds.ToList();
+ var baseQuery = idList.Count == 0
? dbContext.Set()
- : dbContext.Set().Where(x => timeTickerIds.Contains(x.Id));
+ : dbContext.Set().Where(x => idList.Contains(x.Id));
await baseQuery
.WhereCanAcquire(_lockHolder)
@@ -127,9 +128,15 @@ public async Task UpdateTimeTicker(InternalFunctionContext functionContexts
public async Task UpdateTimeTickersWithUnifiedContext(Guid[] timeTickerIds, InternalFunctionContext functionContext, CancellationToken cancellationToken = default)
{
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);;
+ var idList = timeTickerIds.ToList();
await dbContext.Set()
+<<<<<<< HEAD
.Where(x => timeTickerIds.Contains(x.Id))
.ExecuteUpdateAsync(MappingExtensions.UpdateTimeTicker(functionContext, _clock.UtcNow), cancellationToken).ConfigureAwait(false);
+=======
+ .Where(x => idList.Contains(x.Id))
+ .ExecuteUpdateAsync(setter => setter.UpdateTimeTicker(functionContext, _clock.UtcNow), cancellationToken).ConfigureAwait(false);
+>>>>>>> 39b9b90 (Fix .NET 9+ EF Core query failures caused by ReadOnlySpan array.Contains() (#574))
}
public async Task GetEarliestTimeTickers(CancellationToken cancellationToken)
@@ -214,10 +221,11 @@ public async Task AcquireImmediateTimeTickersAsync(Guid[] id
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
var now = _clock.UtcNow;
+ var idList = ids.ToList();
// Acquire and mark InProgress in a single update
var affected = await dbContext.Set()
- .Where(x => ids.Contains(x.Id))
+ .Where(x => idList.Contains(x.Id))
.WhereCanAcquire(_lockHolder)
.ExecuteUpdateAsync(setter => setter
.SetProperty(x => x.LockHolder, _lockHolder)
@@ -232,7 +240,7 @@ public async Task AcquireImmediateTimeTickersAsync(Guid[] id
// Return the acquired tickers for immediate execution, with children
return await dbContext.Set()
.AsNoTracking()
- .Where(x => ids.Contains(x.Id) && x.LockHolder == _lockHolder && x.Status == TickerStatus.InProgress)
+ .Where(x => idList.Contains(x.Id) && x.LockHolder == _lockHolder && x.Status == TickerStatus.InProgress)
.Include(x => x.Children.Where(y => y.ExecutionTime == null))
.Select(MappingExtensions.ForQueueTimeTickers())
.ToArrayAsync(cancellationToken)
@@ -245,7 +253,7 @@ public async Task MigrateDefinedCronTickers((string Function, string Expression)
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
var now = _clock.UtcNow;
- var functions = cronTickers.Select(x => x.Function).ToArray();
+ var functions = cronTickers.Select(x => x.Function).ToList();
var cronSet = dbContext.Set();
// Build the complete set of registered function names to detect orphaned tickers.
@@ -262,16 +270,17 @@ public async Task MigrateDefinedCronTickers((string Function, string Expression)
.ToArrayAsync(cancellationToken)
.ConfigureAwait(false);
- if (orphanedCron.Length > 0)
+ var orphanedCronList = orphanedCron.ToList();
+ if (orphanedCronList.Count > 0)
{
// Delete related occurrences first (if any), then the cron tickers
await dbContext.Set>()
- .Where(o => orphanedCron.Contains(o.CronTickerId))
+ .Where(o => orphanedCronList.Contains(o.CronTickerId))
.ExecuteDeleteAsync(cancellationToken)
.ConfigureAwait(false);
await cronSet
- .Where(c => orphanedCron.Contains(c.Id))
+ .Where(c => orphanedCronList.Contains(c.Id))
.ExecuteDeleteAsync(cancellationToken)
.ConfigureAwait(false);
}
@@ -421,9 +430,10 @@ public async Task ReleaseAcquiredCronTickerOccurrences(Guid[] occurrenceIds, Can
var now = _clock.UtcNow;
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);;
- var baseQuery = occurrenceIds.Length == 0
- ? dbContext.Set>()
- : dbContext.Set>().Where(x => occurrenceIds.Contains(x.Id));
+ var idList = occurrenceIds.ToList();
+ var baseQuery = idList.Count == 0
+ ? dbContext.Set>()
+ : dbContext.Set>().Where(x => idList.Contains(x.Id));
await baseQuery
.WhereCanAcquire(_lockHolder)
@@ -525,11 +535,12 @@ public async Task> GetEarliestAvailableC
{
var now = _clock.UtcNow;
var mainSchedulerThreshold = now.AddSeconds(-1);
+ var idList = ids.ToList();
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);;
return await dbContext.Set>()
.AsNoTracking()
.Include(x => x.CronTicker)
- .Where(x => ids.Contains(x.CronTickerId))
+ .Where(x => idList.Contains(x.CronTickerId))
.Where(x => x.ExecutionTime >= mainSchedulerThreshold) // Only items within the 1-second main scheduler window
.WhereCanAcquire(_lockHolder)
.OrderBy(x => x.ExecutionTime)
@@ -554,9 +565,15 @@ public async Task UpdateCronTickerOccurrencesWithUnifiedContext(Guid[] cronOccur
CancellationToken cancellationToken = default)
{
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);;
+ var idList = cronOccurrenceIds.ToList();
await dbContext.Set>()
+<<<<<<< HEAD
.Where(x => cronOccurrenceIds.Contains(x.Id))
.ExecuteUpdateAsync(MappingExtensions.UpdateCronTickerOccurrence(functionContext), cancellationToken)
+=======
+ .Where(x => idList.Contains(x.Id))
+ .ExecuteUpdateAsync(setter => setter.UpdateCronTickerOccurrence(functionContext), cancellationToken)
+>>>>>>> 39b9b90 (Fix .NET 9+ EF Core query failures caused by ReadOnlySpan array.Contains() (#574))
.ConfigureAwait(false);
}
diff --git a/src/TickerQ.EntityFrameworkCore/Infrastructure/TickerEFCorePersistenceProvider.cs b/src/TickerQ.EntityFrameworkCore/Infrastructure/TickerEFCorePersistenceProvider.cs
index c4b0cc11..2e3b1328 100644
--- a/src/TickerQ.EntityFrameworkCore/Infrastructure/TickerEFCorePersistenceProvider.cs
+++ b/src/TickerQ.EntityFrameworkCore/Infrastructure/TickerEFCorePersistenceProvider.cs
@@ -101,10 +101,11 @@ public async Task RemoveTimeTickers(Guid[] timeTickerIds, CancellationToken
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);;
// Load the entities to be deleted (including children for cascade delete)
+ var idList = timeTickerIds.ToList();
var tickersToDelete = await dbContext.Set()
.Include(x => x.Children)
.ThenInclude(x => x.Children) // Include grandchildren if needed
- .Where(x => timeTickerIds.Contains(x.Id))
+ .Where(x => idList.Contains(x.Id))
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
@@ -189,7 +190,8 @@ public async Task UpdateCronTickers(TCronTicker[] cronTickers, Cancellation
public async Task RemoveCronTickers(Guid[] cronTickerIds, CancellationToken cancellationToken)
{
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
- var result = await dbContext.Set().Where(x => cronTickerIds.Contains(x.Id))
+ var idList = cronTickerIds.ToList();
+ var result = await dbContext.Set().Where(x => idList.Contains(x.Id))
.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
if(RedisContext.HasRedisConnection)
@@ -246,8 +248,9 @@ public async Task InsertCronTickerOccurrences(CronTickerOccurrenceEntity RemoveCronTickerOccurrences(Guid[] cronTickerOccurrences, CancellationToken cancellationToken = default)
{
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
+ var idList = cronTickerOccurrences.ToList();
return await dbContext.Set>()
- .Where(x => cronTickerOccurrences.Contains(x.Id))
+ .Where(x => idList.Contains(x.Id))
.ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
}
@@ -258,10 +261,11 @@ public async Task[]> AcquireImmediateCro
await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
var now = _clock.UtcNow;
+ var idList = occurrenceIds.ToList();
// Only acquire occurrences that are acquirable (Idle/Queued and not locked by another node)
var query = dbContext.Set>()
- .Where(x => occurrenceIds.Contains(x.Id))
+ .Where(x => idList.Contains(x.Id))
.WhereCanAcquire(_lockHolder);
// Lock and mark InProgress
@@ -279,7 +283,7 @@ public async Task[]> AcquireImmediateCro
// Return acquired occurrences with CronTicker populated
return await dbContext.Set>()
.AsNoTracking()
- .Where(x => occurrenceIds.Contains(x.Id) && x.LockHolder == _lockHolder && x.Status == TickerStatus.InProgress)
+ .Where(x => idList.Contains(x.Id) && x.LockHolder == _lockHolder && x.Status == TickerStatus.InProgress)
.Include(x => x.CronTicker)
.ToArrayAsync(cancellationToken)
.ConfigureAwait(false);