From 684e980633de66c31603d6999acf14e02edb672c Mon Sep 17 00:00:00 2001
From: Steve Lorello <42971704+slorello89@users.noreply.github.com>
Date: Mon, 12 Sep 2022 03:56:18 -0400
Subject: [PATCH] allows users to not save enumerated data in the StateManager
(#194)
---
src/Redis.OM/RedisConnectionProvider.cs | 10 +++++
src/Redis.OM/SearchExtensions.cs | 14 +++----
src/Redis.OM/Searching/IRedisCollection.cs | 5 +++
src/Redis.OM/Searching/RedisCollection.cs | 40 +++++++++++++++++--
.../Searching/RedisCollectionEnumerator.cs | 10 ++++-
src/Redis.OM/Searching/RedisQueryProvider.cs | 2 +-
.../RediSearchTests/SearchFunctionalTests.cs | 23 +++++++++++
7 files changed, 92 insertions(+), 12 deletions(-)
diff --git a/src/Redis.OM/RedisConnectionProvider.cs b/src/Redis.OM/RedisConnectionProvider.cs
index 8c531148..8a361442 100644
--- a/src/Redis.OM/RedisConnectionProvider.cs
+++ b/src/Redis.OM/RedisConnectionProvider.cs
@@ -70,5 +70,15 @@ public RedisConnectionProvider(IConnectionMultiplexer connectionMultiplexer)
/// A RedisCollection.
public IRedisCollection RedisCollection(int chunkSize = 100)
where T : notnull => new RedisCollection(Connection, chunkSize);
+
+ ///
+ /// Gets a redis collection.
+ ///
+ /// The type the collection will be retrieving.
+ /// Whether or not the RedisCollection should maintain the state of documents it enumerates.
+ /// Size of chunks to use during pagination, larger chunks = larger payloads returned but fewer round trips.
+ /// A RedisCollection.
+ public IRedisCollection RedisCollection(bool saveState, int chunkSize = 100)
+ where T : notnull => new RedisCollection(Connection, saveState, chunkSize);
}
}
diff --git a/src/Redis.OM/SearchExtensions.cs b/src/Redis.OM/SearchExtensions.cs
index 77b11de7..e42a6ca4 100644
--- a/src/Redis.OM/SearchExtensions.cs
+++ b/src/Redis.OM/SearchExtensions.cs
@@ -97,7 +97,7 @@ public static IRedisCollection Where(this IRedisCollection source, Expr
null,
GetMethodInfo(Where, source, expression),
new[] { source.Expression, Expression.Quote(expression) });
- return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, combined, source.ChunkSize);
+ return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, combined, source.SaveState, source.ChunkSize);
}
///
@@ -116,7 +116,7 @@ public static IRedisCollection Select(this IRedisCollection source
null,
GetMethodInfo(Select, source, expression),
new[] { source.Expression, Expression.Quote(expression) });
- return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, null, source.ChunkSize);
+ return new RedisCollection
((RedisQueryProvider)source.Provider, exp, source.StateManager, null, source.SaveState, source.ChunkSize);
}
///
@@ -134,7 +134,7 @@ public static IRedisCollection Skip(this IRedisCollection source, int c
null,
GetMethodInfo(Skip, source, count),
new[] { source.Expression, Expression.Constant(count) });
- return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, collection.BooleanExpression, source.ChunkSize);
+ return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, collection.BooleanExpression, source.SaveState, source.ChunkSize);
}
///
@@ -152,7 +152,7 @@ public static IRedisCollection Take(this IRedisCollection source, int c
null,
GetMethodInfo(Take, source, count),
new[] { source.Expression, Expression.Constant(count) });
- return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, collection.BooleanExpression, source.ChunkSize);
+ return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, collection.BooleanExpression, source.SaveState, source.ChunkSize);
}
///
@@ -480,7 +480,7 @@ public static IRedisCollection GeoFilter(this IRedisCollection source,
Expression.Constant(lat),
Expression.Constant(radius),
Expression.Constant(unit));
- return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, collection.BooleanExpression, source.ChunkSize);
+ return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, collection.BooleanExpression, source.SaveState, source.ChunkSize);
}
///
@@ -499,7 +499,7 @@ public static IRedisCollection OrderBy(this IRedisCollection so
null,
GetMethodInfo(OrderBy, source, expression),
new[] { source.Expression, Expression.Quote(expression) });
- return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, collection.BooleanExpression, source.ChunkSize);
+ return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, collection.BooleanExpression, source.SaveState, source.ChunkSize);
}
///
@@ -518,7 +518,7 @@ public static IRedisCollection OrderByDescending(this IRedisCollec
null,
GetMethodInfo(OrderByDescending, source, expression),
new[] { source.Expression, Expression.Quote(expression) });
- return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, collection.BooleanExpression, source.ChunkSize);
+ return new RedisCollection((RedisQueryProvider)source.Provider, exp, source.StateManager, collection.BooleanExpression, source.SaveState, source.ChunkSize);
}
///
diff --git a/src/Redis.OM/Searching/IRedisCollection.cs b/src/Redis.OM/Searching/IRedisCollection.cs
index 203bf9b6..3b2c38d3 100644
--- a/src/Redis.OM/Searching/IRedisCollection.cs
+++ b/src/Redis.OM/Searching/IRedisCollection.cs
@@ -13,6 +13,11 @@ namespace Redis.OM.Searching
/// The type.
public interface IRedisCollection : IOrderedQueryable, IAsyncEnumerable
{
+ ///
+ /// Gets a value indicating whether gets whether the collection is meant to save the state of the records enumerated into it.
+ ///
+ bool SaveState { get; }
+
///
/// Gets the collection state manager.
///
diff --git a/src/Redis.OM/Searching/RedisCollection.cs b/src/Redis.OM/Searching/RedisCollection.cs
index 54826d26..c4a76b7e 100644
--- a/src/Redis.OM/Searching/RedisCollection.cs
+++ b/src/Redis.OM/Searching/RedisCollection.cs
@@ -31,6 +31,18 @@ public class RedisCollection : IRedisCollection
/// Connection to Redis.
/// Size of chunks to pull back during pagination, defaults to 100.
public RedisCollection(IRedisConnection connection, int chunkSize = 100)
+ : this(connection, true, chunkSize)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Determines whether or not the Redis Colleciton will maintain it's state internally.
+ /// Connection to Redis.
+ /// Size of chunks to pull back during pagination, defaults to 100.
+ /// Thrown if the root attribute of the Redis Colleciton is not decorated with a DocumentAttribute.
+ public RedisCollection(IRedisConnection connection, bool saveState, int chunkSize)
{
var t = typeof(T);
DocumentAttribute rootAttribute = t.GetCustomAttribute();
@@ -41,6 +53,7 @@ public RedisCollection(IRedisConnection connection, int chunkSize = 100)
ChunkSize = chunkSize;
_connection = connection;
+ SaveState = saveState;
StateManager = new RedisCollectionStateManager(rootAttribute);
Initialize(new RedisQueryProvider(connection, StateManager, rootAttribute, ChunkSize), null, null);
}
@@ -51,13 +64,15 @@ public RedisCollection(IRedisConnection connection, int chunkSize = 100)
/// Query Provider.
/// Expression to be parsed for the query.
/// Manager of the internal state of the collection.
+ /// Whether or not the StateManager will maintain the state.
/// Size of chunks to pull back during pagination, defaults to 100.
/// The expression to build the filter from.
- internal RedisCollection(RedisQueryProvider provider, Expression expression, RedisCollectionStateManager stateManager, Expression>? booleanExpression, int chunkSize = 100)
+ internal RedisCollection(RedisQueryProvider provider, Expression expression, RedisCollectionStateManager stateManager, Expression>? booleanExpression, bool saveState, int chunkSize = 100)
{
StateManager = stateManager;
_connection = provider.Connection;
ChunkSize = chunkSize;
+ SaveState = saveState;
Initialize(provider, expression, booleanExpression);
}
@@ -70,6 +85,9 @@ internal RedisCollection(RedisQueryProvider provider, Expression expression, Red
///
public IQueryProvider Provider { get; private set; } = default!;
+ ///
+ public bool SaveState { get; }
+
///
/// Gets manages the state of the items queried from Redis.
///
@@ -484,7 +502,7 @@ public T Single(Expression> expression)
public IEnumerator GetEnumerator()
{
StateManager.Clear();
- return new RedisCollectionEnumerator(Expression, _connection, ChunkSize, StateManager, BooleanExpression);
+ return new RedisCollectionEnumerator(Expression, _connection, ChunkSize, StateManager, BooleanExpression, SaveState);
}
///
@@ -496,6 +514,14 @@ IEnumerator IEnumerable.GetEnumerator()
///
public void Save()
{
+ if (!SaveState)
+ {
+ throw new InvalidOperationException(
+ "The RedisCollection has been instructed to not maintain the state of records enumerated by " +
+ "Redis making the attempt to Save Invalid. Please initialize the RedisCollection with saveState " +
+ "set to true to Save documents in the RedisCollection");
+ }
+
var diff = StateManager.DetectDifferences();
foreach (var item in diff)
{
@@ -516,6 +542,14 @@ public void Save()
///
public async ValueTask SaveAsync()
{
+ if (!SaveState)
+ {
+ throw new InvalidOperationException(
+ "The RedisCollection has been instructed to not maintain the state of records enumerated by " +
+ "Redis making the attempt to Save Invalid. Please initialize the RedisCollection with saveState " +
+ "set to true to Save documents in the RedisCollection");
+ }
+
var diff = StateManager.DetectDifferences();
var tasks = new List>();
foreach (var item in diff)
@@ -600,7 +634,7 @@ public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToke
{
var provider = (RedisQueryProvider)Provider;
StateManager.Clear();
- return new RedisCollectionEnumerator(Expression, provider.Connection, ChunkSize, StateManager, BooleanExpression);
+ return new RedisCollectionEnumerator(Expression, provider.Connection, ChunkSize, StateManager, BooleanExpression, SaveState);
}
private static MethodInfo GetMethodInfo(Func f, T1 unused)
diff --git a/src/Redis.OM/Searching/RedisCollectionEnumerator.cs b/src/Redis.OM/Searching/RedisCollectionEnumerator.cs
index 14e628dd..089e8999 100644
--- a/src/Redis.OM/Searching/RedisCollectionEnumerator.cs
+++ b/src/Redis.OM/Searching/RedisCollectionEnumerator.cs
@@ -22,6 +22,7 @@ internal class RedisCollectionEnumerator : IEnumerator, IAsyncEnumerator _records = new (new RedisReply(new RedisReply[] { 0 }));
@@ -36,7 +37,8 @@ internal class RedisCollectionEnumerator : IEnumerator, IAsyncEnumeratorthe size of a chunk to pull back.
/// the state manager.
/// The main boolean expression to use to build the filter.
- public RedisCollectionEnumerator(Expression exp, IRedisConnection connection, int chunkSize, RedisCollectionStateManager stateManager, Expression>? booleanExpression)
+ /// Determins whether the records from the RedisCollection are stored in the StateManager.
+ public RedisCollectionEnumerator(Expression exp, IRedisConnection connection, int chunkSize, RedisCollectionStateManager stateManager, Expression>? booleanExpression, bool saveState)
{
Type rootType;
var t = typeof(T);
@@ -63,6 +65,7 @@ public RedisCollectionEnumerator(Expression exp, IRedisConnection connection, in
_connection = connection;
_stateManager = stateManager;
+ _saveState = saveState;
}
///
@@ -176,6 +179,11 @@ private async ValueTask GetNextChunkAsync()
private void ConcatenateRecords()
{
+ if (!_saveState)
+ {
+ return;
+ }
+
foreach (var record in _records.Documents)
{
if (_stateManager.Data.ContainsKey(record.Key) || _primitiveType != null)
diff --git a/src/Redis.OM/Searching/RedisQueryProvider.cs b/src/Redis.OM/Searching/RedisQueryProvider.cs
index 1942d069..06b39e5e 100644
--- a/src/Redis.OM/Searching/RedisQueryProvider.cs
+++ b/src/Redis.OM/Searching/RedisQueryProvider.cs
@@ -96,7 +96,7 @@ public IQueryable CreateQuery(Expression expression)
where TElement : notnull
{
var booleanExpression = expression as Expression>;
- return new RedisCollection(this, expression, StateManager, booleanExpression);
+ return new RedisCollection(this, expression, StateManager, booleanExpression, true);
}
///
diff --git a/test/Redis.OM.Unit.Tests/RediSearchTests/SearchFunctionalTests.cs b/test/Redis.OM.Unit.Tests/RediSearchTests/SearchFunctionalTests.cs
index 35dbedb4..0c019350 100644
--- a/test/Redis.OM.Unit.Tests/RediSearchTests/SearchFunctionalTests.cs
+++ b/test/Redis.OM.Unit.Tests/RediSearchTests/SearchFunctionalTests.cs
@@ -810,5 +810,28 @@ public async Task TestMultipleContains()
Assert.Contains(people, x =>x.Id == person1.Id);
Assert.Contains(people, x =>x.Id == person2.Id);
}
+
+ [Fact]
+ public async Task TestShouldFailForSave()
+ {
+ var expectedText = "The RedisCollection has been instructed to not maintain the state of records enumerated by " +
+ "Redis making the attempt to Save Invalid. Please initialize the RedisCollection with saveState " +
+ "set to true to Save documents in the RedisCollection";
+ var collection = new RedisCollection(_connection, false, 100);
+ var ex = await Assert.ThrowsAsync(collection.SaveAsync().AsTask);
+ Assert.Equal(expectedText, ex.Message);
+ ex = Assert.Throws(() =>collection.Save());
+ Assert.Equal(expectedText, ex.Message);
+ }
+
+ [Fact]
+ public async Task TestStatelessCollection()
+ {
+ var collection = new RedisCollection(_connection, false, 10000);
+ var res = await collection.ToListAsync();
+ Assert.True(res.Count >= 1);
+ Assert.Equal(0,collection.StateManager.Data.Count);
+ Assert.Equal(0,collection.StateManager.Snapshot.Count);
+ }
}
}