Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
2853acc
WIP
JoasE Oct 19, 2025
e374631
Wip: merge session tokens
JoasE Oct 20, 2025
f295ce9
WIP..
JoasE Oct 20, 2025
ad8d930
Make Client responsible
JoasE Oct 20, 2025
3a3a510
add todo
JoasE Oct 20, 2025
691aa79
Cleanup and small improvements
JoasE Oct 20, 2025
c4a9977
Cleanup and small improvements
JoasE Oct 20, 2025
ac5ca96
Add option ManualSessionTokenManagementEnabled
JoasE Oct 21, 2025
6f84317
fix no etag sessiontoken match
JoasE Oct 21, 2025
64ab888
Add some testcases
JoasE Oct 21, 2025
dadc58b
Cleanup
JoasE Oct 24, 2025
57f1f37
Add todo
JoasE Oct 24, 2025
fc8a580
Fix empty tokens
JoasE Oct 24, 2025
364d0f4
Rename var
JoasE Oct 24, 2025
c9890e1
Remove some todos
JoasE Oct 24, 2025
255bbe2
Move to non-parsing session token management
JoasE Oct 24, 2025
87e0dfd
Revert "Add option ManualSessionTokenManagementEnabled"
JoasE Oct 24, 2025
df5809e
Cleanup
JoasE Oct 24, 2025
2c29285
Cleanup
JoasE Oct 24, 2025
77a81a3
Add tests for pr comment changes (pooling and query compilation vs ex…
JoasE Oct 25, 2025
81e137c
Clear session token storage on reset
JoasE Oct 25, 2025
0c62cee
Store session token storage in query context instead of compilation c…
JoasE Oct 25, 2025
5c80637
Add todo
JoasE Oct 25, 2025
1f8d6a9
Move to non-parsing but only composing session tokens
JoasE Oct 26, 2025
e729af3
Readd option ManualSessionTokenManagementEnabled
JoasE Oct 28, 2025
9974e8f
Move container checks to extension method and other small improvement…
JoasE Nov 1, 2025
017ffc7
Cleanup
JoasE Nov 1, 2025
0342b9c
Fix make public api virtual
JoasE Nov 1, 2025
8bd06b4
Cleanup & don't return null values in dictionary
JoasE Nov 1, 2025
34eeeea
WIP
JoasE Nov 6, 2025
e0b9ced
Improve tests and sessiontokenstorage
JoasE Nov 6, 2025
7f4105b
Use cosmos strings
JoasE Nov 6, 2025
20c2f1e
Add tests and fix cases
JoasE Nov 10, 2025
7427a65
Do not use automatic session tokens for manual mode if not provided.
JoasE Nov 10, 2025
8d46fed
Cleanup
JoasE Nov 10, 2025
9f4b366
Merge branch 'main' of https://github.com/JoasE/efcore into feature/#…
JoasE Nov 10, 2025
2ea28cb
Cleanup
JoasE Nov 10, 2025
96937a9
Remove debug only test
JoasE Nov 10, 2025
b51c633
Remove test grouping comments
JoasE Nov 22, 2025
3d1be8c
Fix sloppy mistake const string exception message
JoasE Nov 22, 2025
55c36fd
Do not cache default container name in SessionTokenStorageFactory and…
JoasE Nov 22, 2025
4871811
Remove unused parameter from old code
JoasE Nov 22, 2025
e6f13ec
Move method
JoasE Nov 22, 2025
b2fd34b
Update docs to use links
JoasE Nov 22, 2025
48b5232
Update docs on SessionTokenManagementMode
JoasE Nov 22, 2025
25a9d8c
Update cosmos strings
JoasE Nov 22, 2025
83a16bd
Add test read item throws instead of returning null for session not f…
JoasE Nov 22, 2025
907b6b3
Fix typo
JoasE Nov 22, 2025
2562436
Do not use exception flow for document not found
JoasE Nov 23, 2025
c4eeae1
Move back to private fields for _defaultContainerName and _mode inSes…
JoasE Nov 23, 2025
bc47d4f
Simplify and improve performance of container name checks SessionToke…
JoasE Nov 24, 2025
c850c70
Add test for multiple contexts
JoasE Nov 24, 2025
3e30298
Check if default container exists in SessionTokenStorage
JoasE Nov 24, 2025
a8f4e7c
Allow null or 0 subStatusCode for NotFound document
JoasE Nov 24, 2025
27d1c2e
Use shared fixture where possible
JoasE Nov 24, 2025
f79c642
Cache model metadata per model in SessionTokenStorageFactory
JoasE Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/EFCore.Cosmos/Extensions/CosmosDatabaseFacadeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Storage;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;

// ReSharper disable once CheckNamespace
Expand All @@ -25,6 +26,22 @@ public static class CosmosDatabaseFacadeExtensions
public static CosmosClient GetCosmosClient(this DatabaseFacade databaseFacade)
=> GetService<ISingletonCosmosClientWrapper>(databaseFacade).Client;

/// <summary>
/// Gets <see cref="ISessionTokenStorage"/> used to manage the session tokens for this <see cref="DbContext" />.
/// </summary>
/// <param name="databaseFacade">The <see cref="DatabaseFacade" /> for the context.</param>
/// <returns>The <see cref="ISessionTokenStorage"/>.</returns>
public static ISessionTokenStorage GetSessionTokens(this DatabaseFacade databaseFacade)
{
var db = GetService<IDatabase>(databaseFacade);
if (db is not CosmosDatabaseWrapper dbWrapper)
{
throw new InvalidOperationException(CosmosStrings.CosmosNotInUse);
}

return dbWrapper.SessionTokenStorage;
}

private static TService GetService<TService>(IInfrastructure<IServiceProvider> databaseFacade)
where TService : class
{
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore.Cosmos/Extensions/CosmosModelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ public static class CosmosModelExtensions
public static string? GetDefaultContainer(this IReadOnlyModel model)
=> (string?)model[CosmosAnnotationNames.ContainerName];

/// <summary>
/// Returns the all container names used in the model.
/// </summary>
/// <param name="model">The model.</param>
/// <returns>A set of the names of the containers used in the model.</returns>
public static HashSet<string> GetContainerNames(this IReadOnlyModel model)
=> (HashSet<string>)model[CosmosAnnotationNames.ContainerNames]!;

/// <summary>
/// Sets the default container name.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,19 @@ protected override void ProcessModelAnnotations(
{
annotations.Remove(CosmosAnnotationNames.Throughput);
}

// @TODO: Is this the right place for this?
annotations.Add(CosmosAnnotationNames.ContainerNames, GetContainerNames(model));
}

private HashSet<string> GetContainerNames(IModel model)
=> model.GetEntityTypes()
.Where(et => et.FindPrimaryKey() != null)
.Select(et => et.GetContainer())
.Where(container => container != null)
.Distinct()!
.ToHashSet()!;

/// <summary>
/// Updates the entity type annotations that will be set on the read-only object.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ public static class CosmosAnnotationNames
/// </summary>
public const string ContainerName = Prefix + "ContainerName";

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public const string ContainerNames = Prefix + "ContainerNames"; // @TODO: is this the right way?

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
14 changes: 14 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@
<data name="ContainerContainingPropertyConflict" xml:space="preserve">
<value>The entity type '{entityType}' is mapped to the container '{container}' but it is also configured as being contained in property '{property}'.</value>
</data>
<data name="ContainerNameDoesNotExist" xml:space="preserve">
<value>The container with the name '{containerName}' does not exist.</value>
<comment>string</comment>
</data>
<data name="ContainerNotOnRoot" xml:space="preserve">
<value>An Azure Cosmos DB container name is defined on entity type '{entityType}', which inherits from '{baseEntityType}'. Container names must be defined on the root entity type of a hierarchy.</value>
</data>
Expand Down Expand Up @@ -350,6 +354,9 @@
<data name="SaveChangesAutoTransactionBehaviorAlwaysTriggerAtomicity" xml:space="preserve">
<value>When using AutoTransactionBehavior.Always with the Cosmos DB provider, only 1 entity can be saved at a time when using pre- or post- triggers to ensure atomicity.</value>
</data>
<data name="SessionTokenCanNotBeWhiteSpace" xml:space="preserve">
<value>Session token can not be white space.</value>
</data>
<data name="SingleFirstOrDefaultNotSupportedOnNonNullableQueries" xml:space="preserve">
<value>SingleOrDefault and FirstOrDefault cannot be used Cosmos SQL does not allow Offset without Limit. Consider specifying a 'Take' operation on the query.</value>
</data>
Expand Down
24 changes: 22 additions & 2 deletions src/EFCore.Cosmos/Query/Internal/CosmosQueryCompilationContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Cosmos.Storage;

namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;

/// <summary>
Expand All @@ -9,9 +11,19 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class CosmosQueryCompilationContext(QueryCompilationContextDependencies dependencies, bool async)
: QueryCompilationContext(dependencies, async)
public class CosmosQueryCompilationContext : QueryCompilationContext
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public CosmosQueryCompilationContext(QueryCompilationContextDependencies dependencies, ISessionTokenStorage sessionTokenStorage, bool async) : base(dependencies, async)
{
SessionTokenStorage = sessionTokenStorage;
}

/// <summary>
/// The root entity type being queried.
/// </summary>
Expand Down Expand Up @@ -41,4 +53,12 @@ public class CosmosQueryCompilationContext(QueryCompilationContextDependencies d
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </remarks>
public virtual CosmosAliasManager AliasManager { get; } = new();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual ISessionTokenStorage SessionTokenStorage { get; }
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Cosmos.Storage;

namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;

/// <summary>
Expand All @@ -11,26 +13,41 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
/// </summary>
public class CosmosQueryCompilationContextFactory : IQueryCompilationContextFactory
{
private ISessionTokenStorage? _sessionTokenStorage;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public CosmosQueryCompilationContextFactory(QueryCompilationContextDependencies dependencies)
=> Dependencies = dependencies;
{
Dependencies = dependencies;
}

/// <summary>
/// Dependencies for this service.
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected virtual QueryCompilationContextDependencies Dependencies { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected virtual ISessionTokenStorage SessionTokenStorage => _sessionTokenStorage ??= Dependencies.Context.Database.GetSessionTokens();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual QueryCompilationContext Create(bool async)
=> new CosmosQueryCompilationContext(Dependencies, async);
=> new CosmosQueryCompilationContext(Dependencies, SessionTokenStorage, async);
}
1 change: 1 addition & 0 deletions src/EFCore.Cosmos/Query/Internal/CosmosQueryContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Cosmos.Storage;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;

namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#nullable disable

using Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Storage;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
using Newtonsoft.Json.Linq;

Expand Down Expand Up @@ -34,6 +35,7 @@ private sealed class PagingQueryingEnumerable<T> : IAsyncEnumerable<CosmosPage<T
private readonly string _maxItemCountParameterName;
private readonly string _continuationTokenParameterName;
private readonly string _responseContinuationTokenLimitInKbParameterName;
private readonly ISessionTokenStorage _sessionTokenStorage;

public PagingQueryingEnumerable(
CosmosQueryContext cosmosQueryContext,
Expand All @@ -48,7 +50,8 @@ public PagingQueryingEnumerable(
bool threadSafetyChecksEnabled,
string maxItemCountParameterName,
string continuationTokenParameterName,
string responseContinuationTokenLimitInKbParameterName)
string responseContinuationTokenLimitInKbParameterName,
ISessionTokenStorage sessionTokenStorage)
{
_cosmosQueryContext = cosmosQueryContext;
_sqlExpressionFactory = sqlExpressionFactory;
Expand All @@ -63,7 +66,7 @@ public PagingQueryingEnumerable(
_maxItemCountParameterName = maxItemCountParameterName;
_continuationTokenParameterName = continuationTokenParameterName;
_responseContinuationTokenLimitInKbParameterName = responseContinuationTokenLimitInKbParameterName;

_sessionTokenStorage = sessionTokenStorage;
_cosmosContainer = rootEntityType.GetContainer()
?? throw new UnreachableException("Root entity type without a Cosmos container.");
_cosmosPartitionKey = GeneratePartitionKey(
Expand Down Expand Up @@ -92,10 +95,10 @@ private sealed class AsyncEnumerator : IAsyncEnumerator<CosmosPage<T>>
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
private readonly IDiagnosticsLogger<DbLoggerCategory.Database.Command> _commandLogger;
private readonly bool _standAloneStateManager;
private readonly ISessionTokenStorage _sessionTokenStorage;
private readonly CancellationToken _cancellationToken;
private readonly IConcurrencyDetector _concurrencyDetector;
private readonly IExceptionDetector _exceptionDetector;

private bool _hasExecuted;
private bool _isDisposed;

Expand All @@ -111,6 +114,7 @@ public AsyncEnumerator(PagingQueryingEnumerable<T> queryingEnumerable, Cancellat
_commandLogger = queryingEnumerable._commandLogger;
_standAloneStateManager = queryingEnumerable._standAloneStateManager;
_exceptionDetector = _cosmosQueryContext.ExceptionDetector;
_sessionTokenStorage = queryingEnumerable._sessionTokenStorage;
_cancellationToken = cancellationToken;

_concurrencyDetector = queryingEnumerable._threadSafetyChecksEnabled
Expand Down Expand Up @@ -155,6 +159,8 @@ public async ValueTask<bool> MoveNextAsync()
queryRequestOptions.PartitionKey = _cosmosPartitionKey;
}

queryRequestOptions.SessionToken = _sessionTokenStorage.GetSessionToken(_cosmosContainer);

var cosmosClient = _cosmosQueryContext.CosmosClient;
_commandLogger.ExecutingSqlQuery(_cosmosContainer, _cosmosPartitionKey, sqlQuery);
_cosmosQueryContext.InitializeStateManager(_standAloneStateManager);
Expand All @@ -165,7 +171,7 @@ public async ValueTask<bool> MoveNextAsync()
{
queryRequestOptions.MaxItemCount = maxItemCount;
using var feedIterator = cosmosClient.CreateQuery(
_cosmosContainer, sqlQuery, continuationToken, queryRequestOptions);
_cosmosContainer, sqlQuery, _sessionTokenStorage, continuationToken, queryRequestOptions);

using var responseMessage = await feedIterator.ReadNextAsync(_cancellationToken).ConfigureAwait(false);

Expand Down
Loading
Loading