Skip to content

Commit

Permalink
You can use non-nullable value type members for document identifiers. C…
Browse files Browse the repository at this point in the history
…loses GH-3481
  • Loading branch information
jeremydmiller committed Oct 17, 2024
1 parent d795f81 commit 2d42b5c
Show file tree
Hide file tree
Showing 6 changed files with 1,071 additions and 13 deletions.
5 changes: 4 additions & 1 deletion docs/documents/identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,11 +399,14 @@ public async Task update_a_document_smoke_test()
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/ValueTypeTests/Vogen/guid_based_document_operations.cs#L84-L105' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_insert_the_load_by_strong_typed_identifier' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

::: tip
Marten 7.31.0 "fixed" it so that you don't have to use `Nullable<T>` for the identity member of strong typed identifiers.
:::

As you might infer -- or not -- there's a couple rules and internal behavior:

* The identity selection is done just the same as the primitive types, Marten is either looking for an `id`/`Id` member, or a member decorated with
`[Identity]`
* If Marten is going to assign the identity, you will need to use `Nullable<T>` for the identity member of the document
* There is a new `IQuerySession.LoadAsync<T>(object id)` overload that was specifically built for strong typed identifiers
* For `Guid`-wrapped values, Marten is assigning missing identity values based on its sequential `Guid` support
* For `int` or `long`-wrapped values, Marten is using its HiLo support to define the wrapped values
Expand Down
52 changes: 41 additions & 11 deletions src/Marten/Schema/Identity/ValueTypeIdGeneration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,54 +66,65 @@ public void GenerateCode(GeneratedMethod method, DocumentMapping mapping)
method.Frames.Code($"return {{0}}.{mapping.CodeGen.AccessId};", document);
}

private string innerValueAccessor(DocumentMapping mapping)
{
return mapping.IdMember.GetRawMemberType().IsNullable() ? $"{mapping.IdMember.Name}.Value" : mapping.IdMember.Name;
}

private void generateStringWrapper(GeneratedMethod method, DocumentMapping mapping, Use document)
{
method.Frames.Code($"return {{0}}.{mapping.IdMember.Name}.Value;", document);
method.Frames.Code($"return {{0}}.{innerValueAccessor(mapping)};", document);
}

private void generateLongWrapper(GeneratedMethod method, DocumentMapping mapping, Use document)
{
var isDefault = mapping.IdMember.GetRawMemberType().IsNullable() ? $"{mapping.IdMember.Name} == null" : $"{mapping.IdMember.Name}.Value == default";

var database = Use.Type<IMartenDatabase>();
if (Ctor != null)
{
method.Frames.Code(
$"if ({{0}}.{mapping.IdMember.Name} == null) _setter({{0}}, new {OuterType.FullNameInCode()}({{1}}.Sequences.SequenceFor({{2}}).NextLong()));",
$"if ({{0}}.{isDefault}) _setter({{0}}, new {OuterType.FullNameInCode()}({{1}}.Sequences.SequenceFor({{2}}).NextLong()));",
document, database, mapping.DocumentType);
}
else
{
method.Frames.Code(
$"if ({{0}}.{mapping.IdMember.Name} == null) _setter({{0}}, {OuterType.FullNameInCode()}.{Builder.Name}({{1}}.Sequences.SequenceFor({{2}}).NextLong()));",
$"if ({{0}}.{isDefault}) _setter({{0}}, {OuterType.FullNameInCode()}.{Builder.Name}({{1}}.Sequences.SequenceFor({{2}}).NextLong()));",
document, database, mapping.DocumentType);
}
}

private void generateIntWrapper(GeneratedMethod method, DocumentMapping mapping, Use document)
{
var isDefault = mapping.IdMember.GetRawMemberType().IsNullable() ? $"{mapping.IdMember.Name} == null" : $"{mapping.IdMember.Name}.Value == default";

var database = Use.Type<IMartenDatabase>();
if (Ctor != null)
{
method.Frames.Code(
$"if ({{0}}.{mapping.IdMember.Name} == null) _setter({{0}}, new {OuterType.FullNameInCode()}({{1}}.Sequences.SequenceFor({{2}}).NextInt()));",
$"if ({{0}}.{isDefault}) _setter({{0}}, new {OuterType.FullNameInCode()}({{1}}.Sequences.SequenceFor({{2}}).NextInt()));",
document, database, mapping.DocumentType);
}
else
{
method.Frames.Code(
$"if ({{0}}.{mapping.IdMember.Name} == null) _setter({{0}}, {OuterType.FullNameInCode()}.{Builder.Name}({{1}}.Sequences.SequenceFor({{2}}).NextInt()));",
$"if ({{0}}.{isDefault}) _setter({{0}}, {OuterType.FullNameInCode()}.{Builder.Name}({{1}}.Sequences.SequenceFor({{2}}).NextInt()));",
document, database, mapping.DocumentType);
}
}

private void generateGuidWrapper(GeneratedMethod method, DocumentMapping mapping, Use document)
{
var isDefault = mapping.IdMember.GetRawMemberType().IsNullable() ? $"{mapping.IdMember.Name} == null" : $"{mapping.IdMember.Name}.Value == default";

var newGuid = $"{typeof(CombGuidIdGeneration).FullNameInCode()}.NewGuid()";
var create = Ctor == null
? $"{OuterType.FullNameInCode()}.{Builder.Name}({newGuid})"
: $"new {OuterType.FullNameInCode()}({newGuid})";

method.Frames.Code(
$"if ({{0}}.{mapping.IdMember.Name} == null) _setter({{0}}, {create});",
$"if ({{0}}.{isDefault}) _setter({{0}}, {create});",
document);
}

Expand All @@ -122,7 +133,6 @@ public ISelectClause BuildSelectClause(string tableName)
return _selector.CloneToOtherTable(tableName);
}


public static bool IsCandidate(Type idType, out ValueTypeIdGeneration? idGeneration)
{
if (idType.IsGenericType && idType.IsNullable())
Expand Down Expand Up @@ -231,15 +241,35 @@ public Func<object, T> BuildInnerValueSource<T>()
public void WriteBulkWriterCode(GeneratedMethod load, DocumentMapping mapping)
{
var dbType = PostgresqlProvider.Instance.ToParameterType(SimpleType);
load.Frames.Code($"writer.Write(document.{mapping.IdMember.Name}.Value.{ValueProperty.Name}, {{0}});", dbType);

if (mapping.IdMember.GetRawMemberType().IsNullable())
{
load.Frames.Code($"writer.Write(document.{mapping.IdMember.Name}.Value.{ValueProperty.Name}, {{0}});", dbType);
}
else
{
load.Frames.Code($"writer.Write(document.{mapping.IdMember.Name}.{ValueProperty.Name}, {{0}});", dbType);
}
}

public void WriteBulkWriterCodeAsync(GeneratedMethod load, DocumentMapping mapping)
{
var dbType = PostgresqlProvider.Instance.ToParameterType(SimpleType);
load.Frames.Code(
$"await writer.WriteAsync(document.{mapping.IdMember.Name}.Value.{ValueProperty.Name}, {{0}}, {{1}});",
dbType, Use.Type<CancellationToken>());

if (mapping.IdMember.GetRawMemberType().IsNullable())
{
load.Frames.Code(
$"await writer.WriteAsync(document.{mapping.IdMember.Name}.Value.{ValueProperty.Name}, {{0}}, {{1}});",
dbType, Use.Type<CancellationToken>());
}
else
{
load.Frames.Code(
$"await writer.WriteAsync(document.{mapping.IdMember.Name}.{ValueProperty.Name}, {{0}}, {{1}});",
dbType, Use.Type<CancellationToken>());
}


}
}

Expand Down
Loading

0 comments on commit 2d42b5c

Please sign in to comment.