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
13 changes: 12 additions & 1 deletion src/Marten/Internal/ValueTypeIdentifiedDocumentStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ public void SetIdentity(TDoc document, TSimple identity)
public IIdentitySetter<TDoc, TValueType> Inner { get; }
}

internal class ValueTypeIdentifiedDocumentStorage<TDoc, TSimple, TValueType>: IDocumentStorage<TDoc, TSimple> where TDoc : notnull where TSimple : notnull where TValueType : notnull
internal interface IValueTypeStorage<TDoc, TValueType>
{
IQueryHandler<IReadOnlyList<TDoc>> BuildLoadManyHandler(TValueType[] keys);
}

internal class ValueTypeIdentifiedDocumentStorage<TDoc, TSimple, TValueType>: IDocumentStorage<TDoc, TSimple>, IValueTypeStorage<TDoc, TValueType> where TDoc : notnull where TSimple : notnull where TValueType : notnull
{
private readonly Func<TSimple, TValueType> _converter;
private readonly Func<TValueType,TSimple> _unwrapper;
Expand All @@ -52,6 +57,12 @@ public ValueTypeIdentifiedDocumentStorage(ValueTypeInfo valueTypeInfo, IDocument
_unwrapper = valueTypeInfo.UnWrapper<TValueType, TSimple>();
}

public IQueryHandler<IReadOnlyList<TDoc>> BuildLoadManyHandler(TValueType[] keys)
{
var ids = keys.Select(x => _unwrapper(x)).ToArray();
return new LoadByIdArrayHandler<TDoc, TSimple>(Inner, ids);
}

public IDocumentStorage<TDoc, TValueType> Inner { get; }

public void Apply(ICommandBuilder builder) => Inner.Apply(builder);
Expand Down
39 changes: 38 additions & 1 deletion src/Marten/Linq/QueryHandlers/LoadByIdHandler.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#nullable enable
using System;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JasperFx.Core.Reflection;
Expand All @@ -17,10 +19,12 @@ internal class LoadByIdHandler<T, TId>: IQueryHandler<T> where T : notnull where
{
private readonly TId _id;
private readonly IDocumentStorage<T> storage;
private static readonly Type[] _identityTypes = [typeof(int), typeof(long), typeof(string), typeof(Guid)];

public LoadByIdHandler(IDocumentStorage<T, TId> documentStorage, TId id)
{
storage = documentStorage;

_id = id;
}

Expand All @@ -40,12 +44,24 @@ public void ConfigureCommand(ICommandBuilder sql, IMartenSession session)
sql.Append(storage.FromObject);
sql.Append(" as d where id = ");

sql.AppendParameter(_id);
if (_identityTypes.Contains(typeof(TId)))
{
sql.AppendParameter(_id);
}
else
{
var valueType = ValueTypeInfo.ForType(typeof(TId));
typeof(Appender<,>).CloseAndBuildAs<IAppender<TId>>(valueType, typeof(TId), valueType.SimpleType)
.Append(sql, _id);
}


storage.AddTenancyFilter(sql, session.TenantId);
}




public T Handle(DbDataReader reader, IMartenSession session)
{
var selector = (ISelector<T>)storage.BuildSelector(session);
Expand All @@ -68,3 +84,24 @@ public Task<int> StreamJson(Stream stream, DbDataReader reader, CancellationToke
return reader.As<NpgsqlDataReader>().StreamOne(stream, token);
}
}

internal interface IAppender<TId>
{
public void Append(ICommandBuilder builder, TId id);
}

internal class Appender<TId, TSimple>: IAppender<TId>
{
private readonly ValueTypeInfo _valueType;

public Appender(ValueTypeInfo valueType)
{
_valueType = valueType;
}

public void Append(ICommandBuilder builder, TId id)
{
var simple = _valueType.UnWrapper<TId, TSimple>()(id);
builder.AppendParameter(simple);
}
}
39 changes: 33 additions & 6 deletions src/Marten/Services/BatchQuerying/BatchedQuery.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JasperFx.Core.Reflection;
using JasperFx.Events;
using Marten.Events;
using Marten.Events.Querying;
using Marten.Exceptions;
using Marten.Internal;
using Marten.Internal.Sessions;
using Marten.Internal.Storage;
using Marten.Linq;
Expand All @@ -32,6 +29,12 @@ public BatchedQuery(QuerySession parent)

public IBatchEvents Events => this;

public Task<T?> Load<T>(object id) where T : class
{
var loader = typeof(Loader<>).CloseAndBuildAs<ILoader>(id.GetType());
return loader.Load<T>(id, this);
}

public Task<T?> Load<T>(string id) where T : class
{
return load<T, string>(id);
Expand Down Expand Up @@ -208,8 +211,22 @@ public Task<double> Average<T>(IQueryable<T> queryable) where T : notnull
return addItem<T, double>(queryable, SingleValueMode.Average);
}

private interface ILoader
{
Task<T?> Load<T>(object id, BatchedQuery parent) where T : class;
}

private class Loader<TId>: ILoader
{
public Task<T?> Load<T>(object id, BatchedQuery parent) where T : class
{
return parent.load<T, TId>((TId)id);
}
}

internal class BatchLoadByKeys<TDoc>: IBatchLoadByKeys<TDoc> where TDoc : class
{
private static readonly Type[] _identityTypes = [typeof(int), typeof(long), typeof(Guid), typeof(string)];
private readonly BatchedQuery _parent;

public BatchLoadByKeys(BatchedQuery parent)
Expand All @@ -219,6 +236,10 @@ public BatchLoadByKeys(BatchedQuery parent)

public Task<IReadOnlyList<TDoc>> ById<TKey>(params TKey[] keys)
{
if (typeof(TKey).IsNullable())
throw new ArgumentOutOfRangeException(nameof(TKey),
"Cannot use nullable types as the TKey, you may need to explicitly define the generic argument");

return load(keys);
}

Expand All @@ -229,8 +250,14 @@ public Task<IReadOnlyList<TDoc>> ByIdList<TKey>(IEnumerable<TKey> keys)

private Task<IReadOnlyList<TDoc>> load<TKey>(TKey[] keys)
{
var storage = _parent.Parent.StorageFor<TDoc>();
return _parent.AddItem(new LoadByIdArrayHandler<TDoc, TKey>(storage, keys));
var storage = _parent.Parent.StorageFor<TDoc, TKey>();
if (_identityTypes.Contains(typeof(TKey)))
{
return _parent.AddItem(new LoadByIdArrayHandler<TDoc, TKey>(storage, keys));
}

throw new ArgumentOutOfRangeException(nameof(keys),
"Marten cannot (yet) handle this identity type for this operation");
}
}
}
8 changes: 8 additions & 0 deletions src/Marten/Services/BatchQuerying/IBatchedQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ public interface IBatchedQuery
/// <returns></returns>
Task<T?> Load<T>(Guid id) where T : class;

/// <summary>
/// Load a single document of Type "T" by id
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="id"></param>
/// <returns></returns>
Task<T?> Load<T>(object id) where T : class;

/// <summary>
/// Load a one or more documents of Type "T" by id's
/// </summary>
Expand Down
35 changes: 35 additions & 0 deletions src/ValueTypeTests/using_in_batch_queries.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Threading.Tasks;
using Marten.Services.BatchQuerying;
using Marten.Testing.Harness;
using Shouldly;

namespace ValueTypeTests;

public class using_in_batch_queries : OneOffConfigurationsContext
{
[Fact]
public async Task load_one_at_a_time()
{
var teacher1 = new Teacher();
var teacher2 = new Teacher();
var teacher3 = new Teacher();
var teacher4 = new Teacher();

theSession.Store(teacher1, teacher2, teacher3, teacher4);
await theSession.SaveChangesAsync();

var loaded = await theSession.LoadAsync<Teacher>(teacher4.Id);
loaded.ShouldNotBeNull();

var batch = theSession.CreateBatchQuery();
var teacher1_task = batch.Load<Teacher>(teacher1.Id);
var teacher2_task = batch.Load<Teacher>(teacher2.Id);
var teacher3_task = batch.Load<Teacher>(teacher3.Id);

await batch.Execute();

(await teacher1_task).ShouldNotBeNull();
(await teacher2_task).ShouldNotBeNull();
(await teacher3_task).ShouldNotBeNull();
}
}
Loading