diff --git a/src/TableStorage/AttributedDocumentRepository`1.cs b/src/TableStorage/AttributedDocumentRepository`1.cs index 59b8f32..c27c013 100644 --- a/src/TableStorage/AttributedDocumentRepository`1.cs +++ b/src/TableStorage/AttributedDocumentRepository`1.cs @@ -5,7 +5,7 @@ namespace Devlooped { /// - /// A implementation which relies on the entity type + /// An implementation which relies on the entity type /// being annotated with and , and /// optionally (defaults to type name). /// @@ -13,7 +13,7 @@ namespace Devlooped /// When attributed entities are used, this is a convenient generic implementation for use with /// a dependency injection container, such as in ASP.NET Core: /// - /// services.AddScoped(typeof(ITableRepository<>), typeof(AttributedDocumentRepository<>)); + /// services.AddScoped(typeof(IDocumentRepository<>), typeof(AttributedDocumentRepository<>)); /// /// partial class AttributedDocumentRepository : DocumentRepository where T : class diff --git a/src/TableStorage/AttributedTableRepository`1.cs b/src/TableStorage/AttributedTableRepository`1.cs index 7af3b46..d32b840 100644 --- a/src/TableStorage/AttributedTableRepository`1.cs +++ b/src/TableStorage/AttributedTableRepository`1.cs @@ -5,7 +5,7 @@ namespace Devlooped { /// - /// A implementation which relies on the entity type + /// An implementation which relies on the entity type /// being annotated with and , and /// optionally (defaults to type name). /// diff --git a/src/TableStorage/DocumentPartition.cs b/src/TableStorage/DocumentPartition.cs index 287d932..f417e72 100644 --- a/src/TableStorage/DocumentPartition.cs +++ b/src/TableStorage/DocumentPartition.cs @@ -8,7 +8,7 @@ namespace Devlooped { /// - /// Factory methods to create instances + /// Factory methods to create instances /// that store entities as a serialized document. /// static partial class DocumentPartition @@ -22,14 +22,14 @@ static partial class DocumentPartition public const string DefaultTableName = "Documents"; /// - /// Creates an for the given entity type + /// Creates an for the given entity type /// , using as the table name and the /// Name as the partition key. /// /// The type of entity that the repository will manage. /// The storage account to use. /// Function to retrieve the row key for a given entity. - /// The new . + /// The new . public static IDocumentPartition Create( CloudStorageAccount storageAccount, Func rowKey, @@ -37,7 +37,7 @@ public static IDocumentPartition Create( => Create(storageAccount, DefaultTableName, typeof(T).Name, rowKey); /// - /// Creates an for the given entity type + /// Creates an for the given entity type /// , using the given table name and the /// Name as the partition key. /// @@ -45,7 +45,7 @@ public static IDocumentPartition Create( /// The storage account to use. /// Table name to use. /// Function to retrieve the row key for a given entity. - /// The new . + /// The new . public static IDocumentPartition Create( CloudStorageAccount storageAccount, string tableName, @@ -54,7 +54,7 @@ public static IDocumentPartition Create( => Create(storageAccount, tableName, default, rowKey); /// - /// Creates an for the given entity type + /// Creates an for the given entity type /// . /// /// The type of entity that the repository will manage. @@ -65,7 +65,7 @@ public static IDocumentPartition Create( /// If not provided, the Name will be used. /// Optional function to retrieve the row key for a given entity. /// If not provided, the class will need a property annotated with . - /// The new . + /// The new . public static IDocumentPartition Create( CloudStorageAccount storageAccount, string? tableName = default, diff --git a/src/TableStorage/DocumentPartition`1.cs b/src/TableStorage/DocumentPartition`1.cs index eb674fe..67ac368 100644 --- a/src/TableStorage/DocumentPartition`1.cs +++ b/src/TableStorage/DocumentPartition`1.cs @@ -2,6 +2,7 @@ #nullable enable using System; using System.Collections.Generic; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Table; @@ -11,7 +12,7 @@ namespace Devlooped /// partial class DocumentPartition : IDocumentPartition where T : class { - readonly IDocumentRepository repository; + readonly DocumentRepository repository; /// /// Initializes the repository with the given storage account and optional table name. @@ -50,6 +51,10 @@ public Task DeleteAsync(string rowKey, CancellationToken cancellation = default) public IAsyncEnumerable EnumerateAsync(CancellationToken cancellation = default) => repository.EnumerateAsync(PartitionKey, cancellation); + /// + public IAsyncEnumerable EnumerateAsync(Expression> predicate, CancellationToken cancellation = default) + => repository.EnumerateAsync(e => e.PartitionKey == PartitionKey, cancellation); + /// public Task GetAsync(string rowKey, CancellationToken cancellation = default) => repository.GetAsync(PartitionKey, rowKey, cancellation); diff --git a/src/TableStorage/DocumentRepository.cs b/src/TableStorage/DocumentRepository.cs index ac719c1..26cc631 100644 --- a/src/TableStorage/DocumentRepository.cs +++ b/src/TableStorage/DocumentRepository.cs @@ -6,13 +6,13 @@ namespace Devlooped { /// - /// Factory methods to create instances + /// Factory methods to create instances /// that store entities as a serialized document. /// static partial class DocumentRepository { /// - /// Creates an for the given entity type + /// Creates an for the given entity type /// . /// /// The type of entity that the repository will manage. @@ -23,7 +23,7 @@ static partial class DocumentRepository /// If not provided, the class will need a property annotated with . /// Optional function to retrieve the row key for a given entity. /// If not provided, the class will need a property annotated with . - /// The new . + /// The new . public static IDocumentRepository Create( CloudStorageAccount storageAccount, string? tableName = default, diff --git a/src/TableStorage/DocumentRepository`1.cs b/src/TableStorage/DocumentRepository`1.cs index 5b79949..6a4cdc5 100644 --- a/src/TableStorage/DocumentRepository`1.cs +++ b/src/TableStorage/DocumentRepository`1.cs @@ -2,6 +2,8 @@ #nullable enable using System; using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -12,7 +14,9 @@ namespace Devlooped /// partial class DocumentRepository : IDocumentRepository where T : class { - static readonly string documentVersion = (typeof(T).Assembly.GetName().Version ?? new Version(1, 0)).ToString(2); + static readonly string documentVersion; + static readonly int documentMajorVersion; + static readonly int documentMinorVersion; readonly CloudStorageAccount storageAccount; @@ -23,10 +27,18 @@ partial class DocumentRepository : IDocumentRepository where T : class readonly Func rowKey; readonly Task table; - readonly Func> enumerate; + readonly Func>?, CancellationToken, IAsyncEnumerable> enumerate; readonly Func> get; readonly Func> put; + static DocumentRepository() + { + var version = (typeof(T).Assembly.GetName().Version ?? new Version(1, 0)); + documentVersion = version.ToString(2); + documentMajorVersion = version.Major; + documentMinorVersion = version.Minor; + } + /// /// Initializes the table repository. /// @@ -91,8 +103,12 @@ await table.ExecuteAsync(TableOperation.Delete( } /// - public IAsyncEnumerable EnumerateAsync(string? partitionKey = default, CancellationToken cancellation = default) - => enumerate(partitionKey, cancellation); + public IAsyncEnumerable EnumerateAsync(string? partitionKey = default, CancellationToken cancellation = default) + => enumerate(partitionKey == null ? null : e => e.PartitionKey == partitionKey, cancellation); + + /// + public IAsyncEnumerable EnumerateAsync(Expression> predicate, CancellationToken cancellation = default) + => enumerate(predicate, cancellation); /// public Task GetAsync(string partitionKey, string rowKey, CancellationToken cancellation = default) @@ -104,12 +120,16 @@ public Task PutAsync(T entity, CancellationToken cancellation = default) #region Binary - async IAsyncEnumerable EnumerateBinaryAsync(string? partitionKey = default, [EnumeratorCancellation] CancellationToken cancellation = default) + async IAsyncEnumerable EnumerateBinaryAsync(Expression>? predicate, [EnumeratorCancellation] CancellationToken cancellation = default) { var table = await this.table.ConfigureAwait(false); - var query = new TableQuery(); - if (partitionKey != null) - query = query.Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey)); + var query = table.CreateQuery(); + + if (predicate != null) + { + var expression = Expression.Lambda>(predicate.Body, Expression.Parameter(typeof(BinaryDocumentEntity))); + query = (TableQuery)((IQueryable)query).Where(expression); + } TableContinuationToken? continuation = null; do @@ -158,8 +178,10 @@ async Task PutBinaryAsync(T entity, CancellationToken cancellation = default) { ETag = "*", Document = binarySerializer!.Serialize(entity), - DocumentType = typeof(T).FullName, - DocumentVersion = documentVersion, + Type = typeof(T).FullName, + Version = documentVersion, + MajorVersion = documentMajorVersion, + MinorVersion = documentMinorVersion, }), cancellation) .ConfigureAwait(false); @@ -174,12 +196,16 @@ async Task PutBinaryAsync(T entity, CancellationToken cancellation = default) #region String - async IAsyncEnumerable EnumerateStringAsync(string? partitionKey = default, [EnumeratorCancellation] CancellationToken cancellation = default) + async IAsyncEnumerable EnumerateStringAsync(Expression>? predicate, [EnumeratorCancellation] CancellationToken cancellation = default) { var table = await this.table.ConfigureAwait(false); - var query = new TableQuery(); - if (partitionKey != null) - query = query.Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey)); + var query = table.CreateQuery(); + + if (predicate != null) + { + var expression = Expression.Lambda>(predicate.Body, Expression.Parameter(typeof(DocumentEntity))); + query = (TableQuery)((IQueryable)query).Where(expression); + } TableContinuationToken? continuation = null; do @@ -228,8 +254,10 @@ async Task PutStringAsync(T entity, CancellationToken cancellation = default) { ETag = "*", Document = stringSerializer!.Serialize(entity), - DocumentType = typeof(T).FullName, - DocumentVersion = documentVersion, + Type = typeof(T).FullName, + Version = documentVersion, + MajorVersion = documentMajorVersion, + MinorVersion = documentMinorVersion, }), cancellation) .ConfigureAwait(false); @@ -250,22 +278,26 @@ async Task GetTableAsync(string tableName) return table; } - class BinaryDocumentEntity : TableEntity + class BinaryDocumentEntity : TableEntity, IDocumentEntity { public BinaryDocumentEntity() { } public BinaryDocumentEntity(string partitionKey, string rowKey) : base(partitionKey, rowKey) { } public byte[]? Document { get; set; } - public string? DocumentType { get; set; } - public string? DocumentVersion { get; set; } + public string? Type { get; set; } + public string? Version { get; set; } + public int? MajorVersion { get; set; } + public int? MinorVersion { get; set; } } - class DocumentEntity : TableEntity + class DocumentEntity : TableEntity, IDocumentEntity { public DocumentEntity() { } public DocumentEntity(string partitionKey, string rowKey) : base(partitionKey, rowKey) { } public string? Document { get; set; } - public string? DocumentType { get; set; } - public string? DocumentVersion { get; set; } + public string? Type { get; set; } + public string? Version { get; set; } + public int? MajorVersion { get; set; } + public int? MinorVersion { get; set; } } } } \ No newline at end of file diff --git a/src/TableStorage/IDocumentEntity.cs b/src/TableStorage/IDocumentEntity.cs new file mode 100644 index 0000000..1e56962 --- /dev/null +++ b/src/TableStorage/IDocumentEntity.cs @@ -0,0 +1,32 @@ +// +#nullable enable +using Microsoft.Azure.Cosmos.Table; + +namespace Devlooped +{ + /// + /// Document metadata for querying purposes. + /// + partial interface IDocumentEntity : ITableEntity + { + /// + /// The type of the document, its . + /// + string? Type { get; } + + /// + /// The major.minor version of the assembly the document type belongs to. + /// + string? Version { get; } + + /// + /// The major component of the . + /// + int? MajorVersion { get; } + + /// + /// The minor component of the . + /// + int? MinorVersion { get; } + } +} diff --git a/src/TableStorage/IDocumentPartition`1.cs b/src/TableStorage/IDocumentPartition`1.cs new file mode 100644 index 0000000..5d7eef0 --- /dev/null +++ b/src/TableStorage/IDocumentPartition`1.cs @@ -0,0 +1,31 @@ +// +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading; + +namespace Devlooped +{ + /// + /// A specific partition within an . + /// + /// The type of entity being persisted. + partial interface IDocumentPartition : ITableStoragePartition where T : class + { + /// + /// Queries the document repository for items that match the given . + /// + /// + /// var books = DocumentPartition.Create<Book>(); + /// await foreach (var book in books.EnumerateAsync(x => + /// x.PartitionKey == "Rick Riordan" && + /// x.RowKey.CompareTo("Percy Jackson") >= 0 && + /// x.Version == "1.0")) + /// { + /// Console.WriteLine(book.ISBN); + /// } + /// + public IAsyncEnumerable EnumerateAsync(Expression> predicate, CancellationToken cancellation = default); + } +} diff --git a/src/TableStorage/IDocumentRepository`1.cs b/src/TableStorage/IDocumentRepository`1.cs new file mode 100644 index 0000000..1fa25e6 --- /dev/null +++ b/src/TableStorage/IDocumentRepository`1.cs @@ -0,0 +1,29 @@ +// +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading; + +namespace Devlooped +{ + /// + /// A generic repository that stores entities in table storage as serialized + /// documents. + /// + /// The type of entity being persisted. + partial interface IDocumentRepository : ITableStorage where T : class + { + /// + /// Queries the document repository for items that match the given . + /// + /// + /// var books = DocumentRepository.Create<Book>(); + /// await foreach (var book in books.EnumerateAsync(x => x.PartitionKey == "Rick Riordan" && x.DocumentType )) + /// { + /// Console.WriteLine(book.ISBN); + /// } + /// + public IAsyncEnumerable EnumerateAsync(Expression> predicate, CancellationToken cancellation = default); + } +} \ No newline at end of file diff --git a/src/TableStorage/ITablePartition`1.cs b/src/TableStorage/ITablePartition`1.cs index b0f5b1b..b8f5cc6 100644 --- a/src/TableStorage/ITablePartition`1.cs +++ b/src/TableStorage/ITablePartition`1.cs @@ -9,7 +9,7 @@ namespace Devlooped /// by the entity properties, since they are stored in individual columns. /// /// The type of entity being persisted. - partial interface ITablePartition : IDocumentPartition where T : class + partial interface ITablePartition : ITableStoragePartition where T : class { /// /// Creates a query for use with LINQ expressions. See diff --git a/src/TableStorage/ITableRepository`1.cs b/src/TableStorage/ITableRepository`1.cs index 994b934..264a0af 100644 --- a/src/TableStorage/ITableRepository`1.cs +++ b/src/TableStorage/ITableRepository`1.cs @@ -5,12 +5,12 @@ namespace Devlooped { /// - /// A specialized which allows querying + /// A specialized which allows querying /// the repository by the entity properties, since they are stored in individual /// columns. /// /// The type of entity being persisted. - partial interface ITableRepository : IDocumentRepository where T : class + partial interface ITableRepository : ITableStorage where T : class { /// /// Creates a query for use with LINQ expressions. See diff --git a/src/TableStorage/IDocumentPartition.cs b/src/TableStorage/ITableStoragePartition`1.cs similarity index 94% rename from src/TableStorage/IDocumentPartition.cs rename to src/TableStorage/ITableStoragePartition`1.cs index 338b10d..1ba4195 100644 --- a/src/TableStorage/IDocumentPartition.cs +++ b/src/TableStorage/ITableStoragePartition`1.cs @@ -7,10 +7,10 @@ namespace Devlooped { /// - /// A specific partition within a . + /// A specific partition within an . /// /// The type of entity being persisted. - partial interface IDocumentPartition where T : class + partial interface ITableStoragePartition where T : class { /// /// Gets the table name being used. diff --git a/src/TableStorage/IDocumentRepository.cs b/src/TableStorage/ITableStorage`1.cs similarity index 97% rename from src/TableStorage/IDocumentRepository.cs rename to src/TableStorage/ITableStorage`1.cs index d0b17a6..2fae258 100644 --- a/src/TableStorage/IDocumentRepository.cs +++ b/src/TableStorage/ITableStorage`1.cs @@ -10,7 +10,7 @@ namespace Devlooped /// A generic repository that stores entities in table storage. /// /// The type of entity being persisted. - partial interface IDocumentRepository where T : class + partial interface ITableStorage where T : class { /// /// Gets the table name being used. diff --git a/src/TableStorage/TableEntityPartition.cs b/src/TableStorage/TableEntityPartition.cs index 7019f57..7080829 100644 --- a/src/TableStorage/TableEntityPartition.cs +++ b/src/TableStorage/TableEntityPartition.cs @@ -2,6 +2,7 @@ #nullable enable using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Table; @@ -9,9 +10,9 @@ namespace Devlooped { /// - partial class TableEntityPartition : IDocumentPartition + partial class TableEntityPartition : ITablePartition { - readonly IDocumentRepository repository; + readonly TableEntityRepository repository; /// /// Initializes the repository with the given storage account and optional table name. @@ -32,6 +33,9 @@ protected internal TableEntityPartition(CloudStorageAccount storageAccount, stri /// public string PartitionKey { get; } + /// + public IQueryable CreateQuery() => repository.CreateQuery().Where(x => x.PartitionKey == PartitionKey); + /// public async Task DeleteAsync(TableEntity entity, CancellationToken cancellation = default) { diff --git a/src/TableStorage/TableEntityRepository.cs b/src/TableStorage/TableEntityRepository.cs index e730eb4..bb6f039 100644 --- a/src/TableStorage/TableEntityRepository.cs +++ b/src/TableStorage/TableEntityRepository.cs @@ -1,6 +1,7 @@ // #nullable enable using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -9,7 +10,7 @@ namespace Devlooped { /// - partial class TableEntityRepository : IDocumentRepository + partial class TableEntityRepository : ITableRepository { readonly CloudStorageAccount storageAccount; readonly Task table; @@ -29,6 +30,10 @@ protected internal TableEntityRepository(CloudStorageAccount storageAccount, str /// public string TableName { get; } + /// + public IQueryable CreateQuery() + => storageAccount.CreateCloudTableClient().GetTableReference(TableName).CreateQuery(); + /// public async Task DeleteAsync(string partitionKey, string rowKey, CancellationToken cancellation = default) { diff --git a/src/TableStorage/TablePartition.cs b/src/TableStorage/TablePartition.cs index fd9dd0e..6fd741b 100644 --- a/src/TableStorage/TablePartition.cs +++ b/src/TableStorage/TablePartition.cs @@ -9,7 +9,7 @@ namespace Devlooped { /// - /// Factory methods to create instances + /// Factory methods to create instances /// that store entities using individual columns for entity properties. /// static partial class TablePartition @@ -23,13 +23,13 @@ static partial class TablePartition public const string DefaultTableName = "Entities"; /// - /// Creates an , using + /// Creates an , using /// as the table name and the /// Name as the partition key. /// /// The storage account to use. - /// The new . - public static IDocumentPartition Create(CloudStorageAccount storageAccount, string tableName, string partitionKey) + /// The new . + public static ITablePartition Create(CloudStorageAccount storageAccount, string tableName, string partitionKey) => new TableEntityPartition(storageAccount, tableName, partitionKey); /// @@ -40,7 +40,7 @@ public static IDocumentPartition Create(CloudStorageAccount storage /// The type of entity that the repository will manage. /// The storage account to use. /// Function to retrieve the row key for a given entity. - /// The new . + /// The new . public static ITablePartition Create( CloudStorageAccount storageAccount, Expression> rowKey) where T : class @@ -74,7 +74,7 @@ public static ITablePartition Create( /// If not provided, the Name will be used. /// Optional function to retrieve the row key for a given entity. /// If not provided, the class will need a property annotated with . - /// The new . + /// The new . public static ITablePartition Create( CloudStorageAccount storageAccount, string? tableName = default, diff --git a/src/TableStorage/TableRepository.cs b/src/TableStorage/TableRepository.cs index d8be781..e53246b 100644 --- a/src/TableStorage/TableRepository.cs +++ b/src/TableStorage/TableRepository.cs @@ -9,7 +9,7 @@ namespace Devlooped { /// - /// Factory methods to create instances + /// Factory methods to create instances /// that store entities using individual columns for entity properties. /// static partial class TableRepository @@ -17,18 +17,18 @@ static partial class TableRepository static readonly ConcurrentDictionary defaultTableNames = new(); /// - /// Creates an repository. + /// Creates an repository. /// /// The storage account to use. /// Table name to use. - /// The new . - public static IDocumentRepository Create( + /// The new . + public static ITableRepository Create( CloudStorageAccount storageAccount, string tableName) => new TableEntityRepository(storageAccount, tableName); /// - /// Creates an for the given entity type + /// Creates an for the given entity type /// , using the Name as /// the table name. /// @@ -36,7 +36,7 @@ public static IDocumentRepository Create( /// The storage account to use. /// Function to retrieve the partition key for a given entity. /// Function to retrieve the row key for a given entity. - /// The new . + /// The new . public static ITableRepository Create( CloudStorageAccount storageAccount, Expression> partitionKey, @@ -44,7 +44,7 @@ public static ITableRepository Create( => Create(storageAccount, typeof(T).Name, partitionKey, rowKey); /// - /// Creates an for the given entity type + /// Creates an for the given entity type /// . /// /// The type of entity that the repository will manage. @@ -55,7 +55,7 @@ public static ITableRepository Create( /// If not provided, the class will need a property annotated with . /// Optional function to retrieve the row key for a given entity. /// If not provided, the class will need a property annotated with . - /// The new . + /// The new . public static ITableRepository Create( CloudStorageAccount storageAccount, string? tableName = default, diff --git a/src/TableStorage/Visibility.cs b/src/TableStorage/Visibility.cs index b7ec6fd..ba4b264 100644 --- a/src/TableStorage/Visibility.cs +++ b/src/TableStorage/Visibility.cs @@ -2,13 +2,17 @@ namespace Devlooped { // Sets default visibility when using compiled version, where everything is public - public partial interface IDocumentRepository { } + public partial interface ITableStorage { } + public partial interface ITableStoragePartition { } + public partial interface ITableRepository { } public partial interface ITableRepository { } - public partial interface IDocumentPartition { } public partial interface ITablePartition { } + public partial interface IDocumentRepository { } + public partial interface IDocumentPartition { } public partial interface IDocumentSerializer { } public partial interface IBinaryDocumentSerializer { } public partial interface IStringDocumentSerializer { } + public partial interface IDocumentEntity { } public partial class TableRepository { } public partial class TableRepository { } diff --git a/src/Tests/DocumentRepositoryTests.cs b/src/Tests/DocumentRepositoryTests.cs index 1139e0f..a95b24a 100644 --- a/src/Tests/DocumentRepositoryTests.cs +++ b/src/Tests/DocumentRepositoryTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Threading.Tasks; using MessagePack; using Microsoft.Azure.Cosmos.Table; @@ -125,6 +126,51 @@ public async Task DocumentPartitionEndToEnd(IDocumentSerializer serializer) } } + [Theory] + [MemberData(nameof(Serializers))] + public async Task CanQueryDocument(IDocumentSerializer serializer) + { + var table = CloudStorageAccount.DevelopmentStorageAccount.CreateCloudTableClient() + .GetTableReference(nameof(CanQueryDocument) + serializer.GetType().Name); + await table.DeleteIfExistsAsync(); + await table.CreateAsync(); + + try + { + var repo = DocumentRepository.Create(CloudStorageAccount.DevelopmentStorageAccount, + table.Name, serializer: serializer); + + var partitionKey = "P" + Guid.NewGuid().ToString("N"); + + await repo.PutAsync(new DocumentEntity + { + PartitionKey = partitionKey, + RowKey = "Bar", + Title = "Bar", + }); + + await repo.PutAsync(new DocumentEntity + { + PartitionKey = partitionKey, + RowKey = "Foo", + Title = "Foo", + }); + + var entities = await repo.EnumerateAsync(e => + e.PartitionKey == partitionKey && + e.RowKey.CompareTo("Foo") >= 0 && e.RowKey.CompareTo("Fop") < 0 && + e.Version != "1.0" && + e.Type == typeof(DocumentEntity).FullName) + .ToListAsync(); + + Assert.Single(entities); + } + finally + { + await table.DeleteIfExistsAsync(); + } + } + [ProtoContract] [MessagePackObject] public class DocumentEntity diff --git a/src/Tests/QueryTests.cs b/src/Tests/QueryTests.cs index bb59422..0e2b2f1 100644 --- a/src/Tests/QueryTests.cs +++ b/src/Tests/QueryTests.cs @@ -127,6 +127,29 @@ public async Task CanFilterByColumn() Assert.Single(result); } + [Fact] + public async Task CanFilterDocuments() + { + var account = CloudStorageAccount.DevelopmentStorageAccount; + await LoadBooksAsync(DocumentRepository.Create( + account, nameof(CanFilterDocuments), x => x.Author, x => x.ISBN)); + + var repo = DocumentPartition.Create(account, nameof(CanFilterDocuments), "Rick Riordan", x => x.ISBN); + + //// Get specific set of books from one particular publisher/country combination + //// in this case, 978-[English-speaking country, 1][Disney Editions, 4231] + //// See https://en.wikipedia.org/wiki/List_of_group-1_ISBN_publisher_codes + //var query = from book in repo.CreateQuery() + // where + // book.ISBN.CompareTo("97814231") >= 0 && + // book.ISBN.CompareTo("97814232") < 0 + // select new { book.ISBN, book.Title }; + + //var result = await query.AsAsyncEnumerable().ToListAsync(); + + //Assert.Equal(4, result.Count); + } + [Fact] public async Task EnumFailsInTableClient() { @@ -142,7 +165,7 @@ public async Task EnumFailsInTableClient() .ToList()); } - async Task LoadBooksAsync(ITableRepository books) + async Task LoadBooksAsync(ITableStorage books) { foreach (var book in File.ReadAllLines("Books.csv").Skip(1) .Select(line => line.Split(','))