Skip to content

Commit

Permalink
Add DetachAll and ResetAsync (#596)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored Nov 15, 2024
1 parent d2f941d commit 7f4746f
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 13 deletions.
24 changes: 21 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,26 @@ csharp_style_inlined_variable_declaration = true : none
csharp_style_throw_expression = true : none
csharp_style_conditional_delegate_call = true : none

dotnet_style_object_initializer = true : suggestion
dotnet_style_collection_initializer = true : suggestion
dotnet_style_object_initializer = true : silent
dotnet_style_collection_initializer = true
dotnet_style_coalesce_expression = true : suggestion
dotnet_style_null_propagation = true : suggestion
dotnet_style_explicit_tuple_names = true : suggestion
dotnet_style_explicit_tuple_names = true : suggestion

# IDE0028: Simplify collection initialization
dotnet_diagnostic.IDE0028.severity = silent

# CA1861: Avoid constant arrays as arguments
dotnet_diagnostic.CA1861.severity = silent

# IDE0301: Simplify collection initialization
dotnet_diagnostic.IDE0301.severity = suggestion

# IDE0022: Use block body for method
dotnet_diagnostic.IDE0022.severity = silent

# IDE0032: Use auto property
dotnet_diagnostic.IDE0032.severity = suggestion

# IDE0074: Use compound assignment
dotnet_diagnostic.IDE0074.severity = suggestion
1 change: 1 addition & 0 deletions YesSql.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{0882
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EDE52F22-03E3-4909-8BAB-D5E0B2E0815A}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.github\workflows\build.yml = .github\workflows\build.yml
src\Directory.Build.props = src\Directory.Build.props
src\Directory.Packages.props = src\Directory.Packages.props
Expand Down
14 changes: 13 additions & 1 deletion src/YesSql.Abstractions/ISession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ public interface ISession : IDisposable, IAsyncDisposable
/// </remarks>
void Detach(IEnumerable<object> entries, string collection = null);

/// <summary>
/// Removes all items from the identity map.
/// </summary>
void DetachAll(string collection = null);

/// <summary>
/// Loads objects by id.
/// </summary>
Expand All @@ -93,10 +98,17 @@ public interface ISession : IDisposable, IAsyncDisposable
IQuery<T> ExecuteQuery<T>(ICompiledQuery<T> compiledQuery, string collection = null) where T : class;

/// <summary>
/// Cancels any pending commands.
/// Marks the current session as "canceled" such that any following calls to <see cref="SaveChangesAsync"/> will be ignored.
/// This is useful when multiple components can add operations to the session and one of them fails, making the session invalid.
/// To instead rollback the transaction and revert any pending changes, use <see cref="ResetAsync"/>.
/// </summary>
Task CancelAsync();

/// <summary>
/// Resets the state of the current <see cref="ISession"/> by canceling any pending operations, closing the transaction, putting it back in a usable default state.
/// </summary>
Task ResetAsync();

/// <summary>
/// Flushes pending commands to the database.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/YesSql.Abstractions/SessionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static class SessionExtensions
/// Loads an object by its id.
/// </summary>
/// <returns>The object or <c>null</c>.</returns>
public async static Task<T> GetAsync<T>(this ISession session, long id, string collection = null)
public static async Task<T> GetAsync<T>(this ISession session, long id, string collection = null)
where T : class
=> (await session.GetAsync<T>([id], collection)).FirstOrDefault();

Expand Down
36 changes: 32 additions & 4 deletions src/YesSql.Core/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,28 @@ public void Detach(IEnumerable<object> entries, string collection)
}
}

public void DetachAll(string collection)
{
CheckDisposed();

var state = GetState(collection);

state._concurrent?.Clear();
state._saved?.Clear();
state._updated?.Clear();
state._tracked?.Clear();
state._deleted?.Clear();
state._identityMap?.Clear();
}

public async Task ResetAsync()
{
CheckDisposed();

await ReleaseTransactionAsync();
await ReleaseConnectionAsync();
}

private static void DetachInternal(object entity, SessionState state)
{
state.Saved.Remove(entity);
Expand Down Expand Up @@ -529,7 +551,7 @@ public IEnumerable<T> Get<T>(IList<Document> documents, string collection) where
return Enumerable.Empty<T>();
}

var result = new List<T>();
var result = new List<T>(documents.Count);
var defaultAccessor = _store.GetIdAccessor(typeof(T));
var typeName = Store.TypeNames[typeof(T)];

Expand Down Expand Up @@ -628,7 +650,7 @@ private void CheckDisposed()
Dispose(false);
}

public void Dispose(bool disposing)
public void Dispose(bool _)
{
// Do nothing if Dispose() was already called
if (!_disposed)
Expand Down Expand Up @@ -963,13 +985,16 @@ private async Task ReleaseTransactionAsync()
{
foreach (var state in _collectionStates.Values)
{
// IdentityMap is cleared in ReleaseSession()
state._concurrent?.Clear();
state._saved?.Clear();
state._updated?.Clear();
state._tracked?.Clear();
state._deleted?.Clear();
state._maps?.Clear();

// Clear the identity map as we don't want to return stale data after committing some changes.
// We assume the identity map is part of the unit-of-work.
state._identityMap?.Clear();
}

_commands?.Clear();
Expand Down Expand Up @@ -1044,7 +1069,10 @@ internal bool HasWork()
state.Updated.Count +
state.Tracked.Count +
state.Deleted.Count > 0
) return true;
)
{
return true;
}
}

return false;
Expand Down
97 changes: 93 additions & 4 deletions test/YesSql.Tests/CoreTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Dapper;
using Microsoft.AspNetCore.Hosting.StaticWebAssets;
using System;
using System.Collections.Generic;
using System.Data;
Expand Down Expand Up @@ -915,7 +916,7 @@ public async Task NoSavingChangesShouldRollbackAutoFlush()
}

[Fact]
public async Task ShouldKeepIdentityMapOnCommitAsync()
public async Task IdentityMapShouldBeClearedAfterCommit()
{
await using var session = _store.CreateSession();
var bill = new Person
Expand All @@ -933,7 +934,7 @@ public async Task ShouldKeepIdentityMapOnCommitAsync()

newBill = await session.GetAsync<Person>(bill.Id);

Assert.Equal(bill, newBill);
Assert.NotEqual(bill, newBill);
}

[Fact]
Expand All @@ -954,6 +955,65 @@ public async Task ShouldNotKeepIdentityMapOnCommitAsync()
Assert.NotEqual(bill, newBill);
}

[Fact]
public async Task DetachAllRemoveAllEntitiesFromIdentityMap()
{
await using var session = _store.CreateSession();
var p1 = new Person();
var p2 = new Person();
var p3 = new Person();

await session.SaveAsync(p1);
await session.SaveAsync(p2);
await session.SaveAsync(p3);

await session.SaveChangesAsync();

// SaveChangesAsync should clear the identity mao

var p1a = await session.GetAsync<Person>(p1.Id);
var p2a = await session.GetAsync<Person>(p2.Id);
var p3a = await session.GetAsync<Person>(p3.Id);

Assert.NotEqual(p1, p1a);
Assert.NotEqual(p2, p2a);
Assert.NotEqual(p3, p3a);

// The identity should be valid as we do only reads

var p1b = await session.GetAsync<Person>(p1.Id);
var p2b = await session.GetAsync<Person>(p2.Id);
var p3b = await session.GetAsync<Person>(p3.Id);

Assert.Equal(p1a, p1b);
Assert.Equal(p2a, p2b);
Assert.Equal(p3a, p3b);

// Detach should clear specific items

session.Detach(p1b);

var p1c = await session.GetAsync<Person>(p1.Id);
var p2c = await session.GetAsync<Person>(p2.Id);
var p3c = await session.GetAsync<Person>(p3.Id);

Assert.NotEqual(p1a, p1c);
Assert.Equal(p2a, p2c);
Assert.Equal(p3a, p3c);

// DetachAll should clear the identity mao

session.DetachAll();

var p1d = await session.GetAsync<Person>(p1.Id);
var p2d = await session.GetAsync<Person>(p2.Id);
var p3d = await session.GetAsync<Person>(p3.Id);

Assert.NotEqual(p1a, p1d);
Assert.NotEqual(p2a, p2d);
Assert.NotEqual(p3a, p3d);
}

[Fact]
public async Task ShouldUpdateAutoFlushedIndex()
{
Expand Down Expand Up @@ -3248,7 +3308,7 @@ public async Task ShouldQuerySubClasses()
var shapes = await session.Query<Shape, ShapeIndex>(filterType: false).ListAsync();

Assert.Equal(3, shapes.Count());
Assert.Single(shapes.Where(x => x is Circle));
Assert.Single(shapes, x => x is Circle);
Assert.Equal(2, shapes.Where(x => x is Square).Count());
}
}
Expand Down Expand Up @@ -3516,7 +3576,7 @@ public async Task ShouldUpdateDisconnectedObject()
}

[Fact]
public virtual async Task ShouldNotCommitTransaction()
public virtual async Task CancelAsyncShouldNotCommitTransaction()
{
await using (var session = _store.CreateSession())
{
Expand All @@ -3528,6 +3588,7 @@ public virtual async Task ShouldNotCommitTransaction()
await session.SaveAsync(circle);
await session.CancelAsync();

await session.SaveAsync(circle);
await session.SaveChangesAsync();
}

Expand All @@ -3537,6 +3598,34 @@ public virtual async Task ShouldNotCommitTransaction()
}
}

[Fact]
public virtual async Task ResetAsyncShouldKeepSessionUsable()
{
await using (var session = _store.CreateSession())
{
var circle = new Circle
{
Radius = 10
};

await session.SaveAsync(circle);
await session.ResetAsync();

circle = new Circle
{
Radius = 10
};

await session.SaveAsync(circle);
await session.SaveChangesAsync();
}

await using (var session = _store.CreateSession())
{
Assert.Equal(1, await session.Query().For<Circle>().CountAsync());
}
}

[Fact]
public virtual async Task ShouldNotCreateDocumentInCanceledSessions()
{
Expand Down

0 comments on commit 7f4746f

Please sign in to comment.