Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DetachAll and ResetAsync #596

Merged
merged 3 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading