Skip to content
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
18 changes: 18 additions & 0 deletions src/DocumentDbTests/Deleting/soft_deletes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,24 @@ public async Task should_partition_through_attribute()
partitioning.Partitions.Single().ShouldBe(new ListPartition("deleted", "true"));

}

[Fact]
public async Task delete_sets_ISoftDeleted_properties_on_in_memory_document()
{
var doc1 = new SoftDeletedDocument { Id = "50" };
theSession.Store(doc1);
await theSession.SaveChangesAsync();

await using var session2 = theStore.IdentitySession();
var loadedDoc = await session2.LoadAsync<SoftDeletedDocument>(doc1.Id);
loadedDoc.ShouldNotBeNull();

session2.Delete(loadedDoc);
await session2.SaveChangesAsync();

loadedDoc.Deleted.ShouldBeTrue();
loadedDoc.DeletedAt.ShouldNotBeNull();
}
}

public class soft_deletes_with_partitioning: OneOffConfigurationsContext, IAsyncLifetime
Expand Down
27 changes: 27 additions & 0 deletions src/Marten.NodaTime.Testing/Acceptance/noda_time_acceptance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,33 @@ public async Task bug_1276_can_select_instant(SerializerType serializerType)
}
}

[Theory]
[InlineData(SerializerType.SystemTextJson)]
[InlineData(SerializerType.Newtonsoft)]
public async Task can_index_noda_time_types(SerializerType serializerType)
{
StoreOptions(opts =>
{
if (serializerType == SerializerType.Newtonsoft)
{
opts.UseNewtonsoftForSerialization();
}
opts.UseNodaTime();
opts.DatabaseSchemaName = "NodaTime";
opts.Schema.For<TargetWithDates>()
.Duplicate(x => x.LocalDate)
.Duplicate(x => x.LocalDateTime)
.Duplicate(x => x.InstantUTC);
}, true);

await theStore.Advanced.Clean.CompletelyRemoveAllAsync();

// This will apply schema changes, creating indexes on NodaTime fields.
// Previously this would fail with "functions in index expression must be marked IMMUTABLE"
await theStore.Storage.ApplyAllConfiguredChangesToDatabaseAsync();
await theStore.Storage.Database.AssertDatabaseMatchesConfigurationAsync();
}

private class CustomJsonSerializer: ISerializer
{
public EnumStorage EnumStorage => throw new NotSupportedException();
Expand Down
2 changes: 2 additions & 0 deletions src/Marten.NodaTime/NodaTimeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public static void UseNodaTime(this StoreOptions storeOptions, bool shouldConfig
SetNodaTimeTypeMappings();
NpgsqlConnection.GlobalTypeMapper.UseNodaTime();

storeOptions.Linq.MemberSources.Insert(0, new NodaTimeMemberSource());

if (!shouldConfigureJsonSerializer) return;

storeOptions.Advanced.ModifySerializer(serializer =>
Expand Down
63 changes: 63 additions & 0 deletions src/Marten.NodaTime/NodaTimeMemberSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Marten.Linq.Members;
using NodaTime;

namespace Marten.NodaTimePlugin;

internal class NodaTimeMemberSource: IMemberSource
{
public bool TryResolve(IQueryableMember parent, StoreOptions options, MemberInfo memberInfo, Type memberType,
[NotNullWhen(true)] out IQueryableMember? member)
{
if (memberType == typeof(LocalDate) || memberType == typeof(LocalDate?))
{
member = new DateOnlyMember(options, parent, options.Serializer().Casing, memberInfo);
return true;
}

if (memberType == typeof(LocalDateTime) || memberType == typeof(LocalDateTime?))
{
member = new DateTimeMember(options, parent, options.Serializer().Casing, memberInfo);
return true;
}

if (memberType == typeof(Instant) || memberType == typeof(Instant?)
|| memberType == typeof(ZonedDateTime) || memberType == typeof(ZonedDateTime?)
|| memberType == typeof(OffsetDateTime) || memberType == typeof(OffsetDateTime?))
{
member = new NodaTimeTimestampTzMember(options, parent, options.Serializer().Casing, memberInfo);
return true;
}

if (memberType == typeof(LocalTime) || memberType == typeof(LocalTime?))
{
member = new TimeOnlyMember(options, parent, options.Serializer().Casing, memberInfo);
return true;
}

member = null;
return false;
}
}

/// <summary>
/// Member class for NodaTime types that map to PostgreSQL 'timestamp with time zone'.
/// Uses the mt_immutable_timestamptz wrapper function for index compatibility
/// without the .NET DateTimeOffset-specific casting in comparisons.
/// </summary>
internal class NodaTimeTimestampTzMember: QueryableMember, IComparableMember
{
public NodaTimeTimestampTzMember(StoreOptions options, IQueryableMember parent, Casing casing, MemberInfo member)
: base(parent, casing, member)
{
TypedLocator = $"{options.DatabaseSchemaName}.mt_immutable_timestamptz({RawLocator})";
}

public override string SelectorForDuplication(string pgType)
{
return TypedLocator.Replace("d.data", "data");
}
}
7 changes: 7 additions & 0 deletions src/Marten/Internal/Sessions/DocumentSessionBase.Deletes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Marten.Exceptions;
using Marten.Internal.Storage;
using Marten.Linq.SqlGeneration;
using Marten.Metadata;

namespace Marten.Internal.Sessions;

Expand All @@ -26,6 +27,12 @@ public void Delete<T>(T entity) where T : notnull
var deletion = documentStorage.DeleteForDocument(entity, TenantId);
_workTracker.Add(deletion);

if (entity is ISoftDeleted softDeleted)
{
softDeleted.Deleted = true;
softDeleted.DeletedAt = DateTimeOffset.UtcNow;
}

documentStorage.Eject(this, entity);

ChangeTrackers.RemoveAll(x => x.Document is T t && documentStorage.IdentityFor(entity).Equals(documentStorage.IdentityFor(t)));
Expand Down
Loading