You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2022/08/03 07:09:58 UTC
[ignite] branch master updated: IGNITE-17206 .NET: Thin client: Add IgniteSet (#10177)
This is an automated email from the ASF dual-hosted git repository.
ptupitsyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new d7c307a6dd1 IGNITE-17206 .NET: Thin client: Add IgniteSet (#10177)
d7c307a6dd1 is described below
commit d7c307a6dd1b0f1874b507cf231347672ec871bb
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Wed Aug 3 10:09:49 2022 +0300
IGNITE-17206 .NET: Thin client: Add IgniteSet (#10177)
* Implement `IgniteSet.withKeepBinary` to support non-Java user classes.
* Add `IIgniteSetClient`, implement all standard `ISet` methods except `IsSubsetOf`, `IsProperSubsetOf`, `Overlaps`, `SymmetricExceptWith` (do not map to Java API).
---
.../src/main/java/org/apache/ignite/IgniteSet.java | 8 +
.../datastructures/CacheDataStructuresManager.java | 2 +-
.../datastructures/GridCacheSetImpl.java | 33 ++-
.../datastructures/GridCacheSetProxy.java | 5 +
.../datastructures/ClientIgniteSetRequest.java | 8 +-
.../Client/Cache/PartitionAwarenessTest.cs | 65 +++++
.../Client/DataStructures/IgniteSetClientTests.cs | 308 +++++++++++++++++++++
.../IgniteSetClientTestsColocated.cs | 37 +++
.../CollectionClientConfiguration.cs | 65 +++++
.../Client/DataStructures/IIgniteSetClient.cs | 63 +++++
.../Apache.Ignite.Core/Client/IIgniteClient.cs | 10 +
.../Apache.Ignite.Core/Impl/Client/ClientOp.cs | 17 +-
.../Impl/Client/DataStructures/IgniteSetClient.cs | 293 ++++++++++++++++++++
.../DataStructures/IgniteSetClientEnumerator.cs | 151 ++++++++++
.../Apache.Ignite.Core/Impl/Client/IgniteClient.cs | 39 +++
15 files changed, 1091 insertions(+), 13 deletions(-)
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSet.java b/modules/core/src/main/java/org/apache/ignite/IgniteSet.java
index 25d55efd8f7..1ec95fbff67 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteSet.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteSet.java
@@ -130,4 +130,12 @@ public interface IgniteSet<T> extends Set<T>, Closeable {
* @throws IgniteException If job failed.
*/
public <R> R affinityCall(IgniteCallable<R> job) throws IgniteException;
+
+ /**
+ * Returns Ignite set that operates on binary objects without deserialization.
+ *
+ * @param <T1> Type of binary objects.
+ * @return New set instance for binary objects.
+ */
+ public <T1> IgniteSet<T1> withKeepBinary();
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/datastructures/CacheDataStructuresManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/datastructures/CacheDataStructuresManager.java
index 271bbe127fe..abf7881a7c7 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/datastructures/CacheDataStructuresManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/datastructures/CacheDataStructuresManager.java
@@ -403,7 +403,7 @@ public class CacheDataStructuresManager extends GridCacheManagerAdapter {
if (set == null) {
GridCacheSetProxy<T> old = setsMap.putIfAbsent(hdr.id(),
- set = new GridCacheSetProxy<>(cctx, new GridCacheSetImpl<T>(cctx, name, hdr)));
+ set = new GridCacheSetProxy<>(cctx, new GridCacheSetImpl<T>(cctx, name, hdr, false)));
if (old != null)
set = old;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastructures/GridCacheSetImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastructures/GridCacheSetImpl.java
index 5965df81df7..6417736f3f1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastructures/GridCacheSetImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastructures/GridCacheSetImpl.java
@@ -99,13 +99,19 @@ public class GridCacheSetImpl<T> extends AbstractCollection<T> implements Ignite
/** Access to affinityRun() and affinityCall() functions. */
private final IgniteCompute compute;
+ /** */
+ private final boolean keepBinary;
+
+ /** */
+ private final GridCacheSetHeader hdr;
+
/**
* @param ctx Cache context.
* @param name Set name.
* @param hdr Set header.
*/
@SuppressWarnings("unchecked")
- public GridCacheSetImpl(GridCacheContext ctx, String name, GridCacheSetHeader hdr) {
+ public GridCacheSetImpl(GridCacheContext ctx, String name, GridCacheSetHeader hdr, boolean keepBinary) {
this.ctx = ctx;
this.name = name;
this.collocated = hdr.collocated();
@@ -116,6 +122,8 @@ public class GridCacheSetImpl<T> extends AbstractCollection<T> implements Ignite
this.log = ctx.logger(GridCacheSetImpl.class);
this.hdrPart = ctx.affinity().partition(setKey);
this.separated = hdr.separated();
+ this.hdr = hdr;
+ this.keepBinary = keepBinary;
}
/** {@inheritDoc} */
@@ -304,7 +312,7 @@ public class GridCacheSetImpl<T> extends AbstractCollection<T> implements Ignite
try {
onAccess();
- try (GridCloseableIterator<T> iter = iterator0()) {
+ try (GridCloseableIterator<T> iter = iterator0(keepBinary)) {
boolean rmv = false;
Set<SetItemKey> rmvKeys = null;
@@ -342,7 +350,7 @@ public class GridCacheSetImpl<T> extends AbstractCollection<T> implements Ignite
try {
onAccess();
- try (GridCloseableIterator<T> iter = iterator0()) {
+ try (GridCloseableIterator<T> iter = iterator0(keepBinary)) {
Collection<SetItemKey> rmvKeys = new ArrayList<>(BATCH_SIZE);
for (T val : iter) {
@@ -368,7 +376,7 @@ public class GridCacheSetImpl<T> extends AbstractCollection<T> implements Ignite
@Override public Iterator<T> iterator() {
onAccess();
- return iterator0();
+ return iterator0(keepBinary);
}
/** {@inheritDoc} */
@@ -389,6 +397,11 @@ public class GridCacheSetImpl<T> extends AbstractCollection<T> implements Ignite
return compute.affinityCall(cache.name(), setKey, job);
}
+ /** {@inheritDoc} */
+ @Override public <T1> IgniteSet<T1> withKeepBinary() {
+ return new GridCacheSetImpl<>(ctx, name, hdr, true);
+ }
+
/** {@inheritDoc} */
@Override public void close() {
try {
@@ -405,9 +418,9 @@ public class GridCacheSetImpl<T> extends AbstractCollection<T> implements Ignite
/**
* @return Closeable iterator.
*/
- private GridCloseableIterator<T> iterator0() {
+ private GridCloseableIterator<T> iterator0(boolean keepBinary) {
try {
- WeakReferenceCloseableIterator<T> it = separated ? separatedCacheIterator() : sharedCacheIterator();
+ WeakReferenceCloseableIterator<T> it = separated ? separatedCacheIterator(keepBinary) : sharedCacheIterator(keepBinary);
if (rmvd) {
ctx.itHolder().removeIterator(it);
@@ -426,10 +439,10 @@ public class GridCacheSetImpl<T> extends AbstractCollection<T> implements Ignite
* @return Shared cache iterator.
*/
@SuppressWarnings("unchecked")
- private WeakReferenceCloseableIterator<T> sharedCacheIterator() throws IgniteCheckedException {
+ private WeakReferenceCloseableIterator<T> sharedCacheIterator(boolean keepBinary) throws IgniteCheckedException {
CacheQuery qry = new GridCacheQueryAdapter<>(ctx, SET, null, null,
new GridSetQueryPredicate<>(id, collocated), collocated ? hdrPart : null,
- false, false, null);
+ false, keepBinary, null);
Collection<ClusterNode> nodes = dataNodes(ctx.affinity().affinityTopologyVersion());
@@ -452,9 +465,9 @@ public class GridCacheSetImpl<T> extends AbstractCollection<T> implements Ignite
* @return Separated cache iterator.
*/
@SuppressWarnings("unchecked")
- private WeakReferenceCloseableIterator<T> separatedCacheIterator() throws IgniteCheckedException {
+ private WeakReferenceCloseableIterator<T> separatedCacheIterator(boolean keepBinary) throws IgniteCheckedException {
GridCloseableIterator iter =
- (GridCloseableIterator)cache.scanIterator(false, new IgniteBiPredicate<Object, Object>() {
+ (GridCloseableIterator)cache.scanIterator(keepBinary, new IgniteBiPredicate<Object, Object>() {
@Override public boolean apply(Object k, Object v) {
return k.getClass() == GridCacheSetItemKey.class;
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastructures/GridCacheSetProxy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastructures/GridCacheSetProxy.java
index a1cb56b301c..e7be06cc349 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/datastructures/GridCacheSetProxy.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/datastructures/GridCacheSetProxy.java
@@ -415,6 +415,11 @@ public class GridCacheSetProxy<T> implements IgniteSet<T>, Externalizable {
return delegate.affinityCall(job);
}
+ /** {@inheritDoc} */
+ @Override public <T1> IgniteSet<T1> withKeepBinary() {
+ return new GridCacheSetProxy<>(cctx, (GridCacheSetImpl<T1>)delegate.<T1>withKeepBinary());
+ }
+
/**
* Enters busy state.
*/
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/datastructures/ClientIgniteSetRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/datastructures/ClientIgniteSetRequest.java
index 988656b2543..629a9f94b7d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/datastructures/ClientIgniteSetRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/datastructures/ClientIgniteSetRequest.java
@@ -87,7 +87,13 @@ public class ClientIgniteSetRequest extends ClientRequest {
*/
protected <T> IgniteSet<T> igniteSet(ClientConnectionContext ctx) {
// Thin client only works in separated mode, because non-separated mode was discontinued earlier.
- return ctx.kernalContext().grid().set(name, cacheId, collocated, true);
+ IgniteSet<T> set = ctx.kernalContext().grid().set(name, cacheId, collocated, true);
+
+ if (set != null) {
+ set = set.withKeepBinary();
+ }
+
+ return set;
}
/**
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/PartitionAwarenessTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/PartitionAwarenessTest.cs
index bf06b57c5f1..d6adb4929ad 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/PartitionAwarenessTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/PartitionAwarenessTest.cs
@@ -489,6 +489,71 @@ namespace Apache.Ignite.Core.Tests.Client.Cache
Assert.AreEqual(gridIdx, GetClientRequestGridIndex("ValueGet", "datastructures.ClientAtomicLong"));
}
+ [Test]
+ [TestCase("default-grp-partitioned-set", null, CacheMode.Partitioned, 1, 1)]
+ [TestCase("default-grp-partitioned-set", null, CacheMode.Partitioned, 2, 0)]
+ [TestCase("default-grp-partitioned-set", null, CacheMode.Partitioned, 3, 0)]
+ [TestCase("default-grp-partitioned-set", null, CacheMode.Partitioned, 4, 1)]
+ [TestCase("custom-grp-partitioned-set", "testIgniteSet1", CacheMode.Partitioned, 1, 1)]
+ [TestCase("custom-grp-partitioned-set", "testIgniteSet1", CacheMode.Partitioned, 3, 0)]
+ [TestCase("custom-grp-replicated-set", "testIgniteSet2", CacheMode.Replicated, 1, 1)]
+ [TestCase("custom-grp-replicated-set", "testIgniteSet2", CacheMode.Replicated, 3, 1)]
+ public void IgniteSet_RequestIsRoutedToPrimaryNode(
+ string name, string groupName, CacheMode cacheMode, int item, int gridIdx)
+ {
+ var cfg = new CollectionClientConfiguration
+ {
+ GroupName = groupName,
+ CacheMode = cacheMode,
+ Backups = 1
+ };
+
+ var igniteSet = Client.GetIgniteSet<int>(name, cfg);
+
+ // Warm up.
+ igniteSet.Add(0);
+ ClearLoggers();
+
+ // Test.
+ igniteSet.Add(item);
+
+ Assert.AreEqual(gridIdx, GetClientRequestGridIndex("ValueAdd", "datastructures.ClientIgniteSet"));
+ }
+
+ [Test]
+ [TestCase("default-grp-partitioned-set-2", null, CacheMode.Partitioned, 1, 0)]
+ [TestCase("default-grp-partitioned-set-2", null, CacheMode.Partitioned, 2, 0)]
+ [TestCase("default-grp-partitioned-set-2", null, CacheMode.Partitioned, 3, 0)]
+ [TestCase("default-grp-partitioned-set-2", null, CacheMode.Partitioned, 4, 0)]
+ [TestCase("custom-grp-partitioned-set-2", "testIgniteSetColocated1", CacheMode.Partitioned, 1, 1)]
+ [TestCase("custom-grp-partitioned-set-2", "testIgniteSetColocated1", CacheMode.Partitioned, 2, 1)]
+ [TestCase("custom-grp-partitioned-set-2", "testIgniteSetColocated1", CacheMode.Partitioned, 3, 1)]
+ [TestCase("custom-grp-replicated-set-2", "testIgniteSetColocated2", CacheMode.Replicated, 1, 1)]
+ [TestCase("custom-grp-replicated-set-2", "testIgniteSetColocated2", CacheMode.Replicated, 2, 1)]
+ [TestCase("custom-grp-replicated-set-2", "testIgniteSetColocated2", CacheMode.Replicated, 3, 1)]
+ public void IgniteSetColocated_RequestIsRoutedToPrimaryNode(
+ string name, string groupName, CacheMode cacheMode, int item, int gridIdx)
+ {
+ var cfg = new CollectionClientConfiguration
+ {
+ GroupName = groupName,
+ CacheMode = cacheMode,
+ Colocated = true,
+ Backups = 1
+ };
+
+ var igniteSet = Client.GetIgniteSet<int>(name, cfg);
+
+ // Warm up.
+ igniteSet.Add(0);
+ ClearLoggers();
+
+ // Test.
+ igniteSet.Add(1);
+
+ Assert.AreEqual(gridIdx, GetClientRequestGridIndex("ValueAdd", "datastructures.ClientIgniteSet"));
+ }
+
protected override IgniteClientConfiguration GetClientConfiguration()
{
var cfg = base.GetClientConfiguration();
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/DataStructures/IgniteSetClientTests.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/DataStructures/IgniteSetClientTests.cs
new file mode 100644
index 00000000000..7fb15658a91
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/DataStructures/IgniteSetClientTests.cs
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Tests.Client.DataStructures
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection;
+ using Apache.Ignite.Core.Cache.Configuration;
+ using Apache.Ignite.Core.Client;
+ using Apache.Ignite.Core.Client.DataStructures;
+ using Apache.Ignite.Core.Tests.Client.Cache;
+ using NUnit.Framework;
+
+ /// <summary>
+ /// Tests for <see cref="IIgniteSetClient{T}"/>.
+ /// Partition awareness tests: <see cref="PartitionAwarenessTest.IgniteSet_RequestIsRoutedToPrimaryNode"/>,
+ /// <see cref="PartitionAwarenessTest.IgniteSetColocated_RequestIsRoutedToPrimaryNode"/>
+ /// </summary>
+ public class IgniteSetClientTests : ClientTestBase
+ {
+ [Test]
+ public void TestCreateAddContainsRemove()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestCreateAddContainsRemove),
+ GetCollectionConfiguration());
+
+ set.Add(1);
+ set.Add(1);
+
+ set.Add(2);
+ set.Add(2);
+
+ set.Remove(1);
+
+ Assert.AreEqual(nameof(TestCreateAddContainsRemove), set.Name);
+ Assert.IsTrue(set.Contains(2));
+ Assert.IsFalse(set.Contains(1));
+ Assert.AreEqual(1, set.Count);
+ Assert.AreEqual(2, set.Single());
+ Assert.IsFalse(set.IsClosed);
+ }
+
+ [Test]
+ public void TestEnumeratorEmptySet()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestEnumeratorEmptySet),
+ GetCollectionConfiguration());
+
+ using var enumerator = set.GetEnumerator();
+
+ Assert.Throws<InvalidOperationException>(() => _ = enumerator.Current);
+ Assert.IsFalse(enumerator.MoveNext());
+ Assert.Throws<InvalidOperationException>(() => _ = enumerator.Current);
+ }
+
+ [Test]
+ public void TestEnumeratorMultiplePages()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestEnumeratorMultiplePages),
+ GetCollectionConfiguration());
+
+ set.PageSize = 2;
+
+ var items = Enumerable.Range(1, 10).ToList();
+ items.ForEach(x => set.Add(x));
+
+ var res = set.ToList();
+
+ CollectionAssert.AreEquivalent(items, res);
+ }
+
+ [Test]
+ public void TestEnumeratorRemoveAllWhileEnumerating()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestEnumeratorRemoveAllWhileEnumerating),
+ new CollectionClientConfiguration());
+
+ set.PageSize = 2;
+
+ var items = Enumerable.Range(1, 10).ToList();
+ items.ForEach(x => set.Add(x));
+
+ using var enumerator = set.GetEnumerator();
+
+ items.ForEach(x => set.Remove(x));
+
+ var res = new List<int>();
+
+ while (enumerator.MoveNext())
+ {
+ res.Add(enumerator.Current);
+ }
+
+ // 2 from current page, 1 from current item cached on server.
+ Assert.AreEqual(3, res.Count);
+ }
+
+ [Test]
+ public void TestClear()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestClear), GetCollectionConfiguration());
+
+ set.UnionWith(new[] { 1, 2 });
+ set.Clear();
+
+ Assert.AreEqual(0, set.Count);
+ Assert.IsFalse(set.Contains(1));
+ Assert.IsFalse(set.Contains(2));
+ }
+
+ [Test]
+ public void TestExceptWith()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestExceptWith), GetCollectionConfiguration());
+
+ set.UnionWith(new[] { 1, 2, 3 });
+ set.ExceptWith(new[] { 1, 3, 5, 7 });
+ set.ExceptWith(Array.Empty<int>());
+
+ Assert.AreEqual(2, set.Single());
+ }
+
+ [Test]
+ public void TestIntersectWith()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestIntersectWith), GetCollectionConfiguration());
+
+ set.UnionWith(new[] { 1, 2, 3 });
+ set.IntersectWith(new[] { 1, 3, 5 });
+
+ CollectionAssert.AreEquivalent(new[] { 1, 3 }, set);
+ }
+
+ [Test]
+ public void TestGetIgniteSetNonExistingReturnsNull()
+ {
+ Assert.IsNull(Client.GetIgniteSet<int>("foobar", null));
+ }
+
+ [Test]
+ public void TestClose()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestClose), GetCollectionConfiguration());
+
+ set.Add(1);
+ set.Close();
+
+ Assert.IsTrue(set.IsClosed);
+
+ var ex = Assert.Throws<IgniteClientException>(() => set.Add(2));
+ Assert.AreEqual("IgniteSet with name 'TestClose' does not exist.", ex.Message);
+ }
+
+ [Test]
+ public void TestConfigurationPropagation()
+ {
+ var cfg = new CollectionClientConfiguration
+ {
+ Backups = 7,
+ Colocated = false,
+ AtomicityMode = CacheAtomicityMode.Transactional,
+ CacheMode = CacheMode.Partitioned,
+ GroupName = "g-" + nameof(TestConfigurationPropagation)
+ };
+
+ var set = Client.GetIgniteSet<int>(nameof(TestConfigurationPropagation), cfg);
+
+ const string cacheName = "datastructures_TRANSACTIONAL_PARTITIONED_7@" +
+ "g-TestConfigurationPropagation#SET_TestConfigurationPropagation";
+
+ var cache = Client.GetCache<int, int>(cacheName);
+
+ Assert.AreEqual(
+ set.GetType().GetField("_cacheId", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(set),
+ cache.GetType().GetField("_id", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(cache));
+ }
+
+ [Test]
+ public void TestCopyTo()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestCopyTo), GetCollectionConfiguration());
+ set.UnionWith(new[] { 1, 2, 3 });
+
+ var target1 = new int[3];
+ set.CopyTo(target1, 0);
+
+ var target2 = new int[4];
+ set.CopyTo(target2, 1);
+
+ CollectionAssert.AreEquivalent(set, target1);
+ CollectionAssert.AreEquivalent(set, target2.Skip(1));
+ }
+
+ [Test]
+ public void TestCopyToInvalidArguments()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestCopyToInvalidArguments), GetCollectionConfiguration());
+ set.UnionWith(new[] { 1, 2, 3 });
+
+ var target1 = new int[3];
+
+ var ex1 = Assert.Throws<ArgumentOutOfRangeException>(() => set.CopyTo(target1, -1));
+ Assert.AreEqual("Non-negative number required. (Parameter 'arrayIndex')\nActual value was -1.",
+ ex1.Message);
+
+ var ex2 = Assert.Throws<ArgumentException>(() => set.CopyTo(target1, 1));
+ Assert.AreEqual("Destination array is not long enough to copy all the items in the collection. " +
+ "Check array index and length. (Parameter 'array')", ex2.Message);
+
+ var ex3 = Assert.Throws<ArgumentException>(() => set.CopyTo(target1, 3));
+ Assert.AreEqual("Destination array is not long enough to copy all the items in the collection. " +
+ "Check array index and length. (Parameter 'array')", ex3.Message);
+ }
+
+ [Test]
+ public void TestUserObjects()
+ {
+ var set = Client.GetIgniteSet<CustomClass>(nameof(TestUserObjects), GetCollectionConfiguration());
+
+ set.Add(new CustomClass { Id = 1, Name = "A" });
+ set.IntersectWith(new[] { new CustomClass { Id = 1, Name = "A" }, new CustomClass { Id = 2, Name = "B" } });
+
+ Assert.IsTrue(set.Contains(new CustomClass { Id = 1, Name = "A" }));
+ Assert.IsFalse(set.Contains(new CustomClass { Id = 1, Name = "a" }));
+
+ var res = set.Single();
+
+ Assert.AreEqual(1, res.Id);
+ Assert.AreEqual("A", res.Name);
+
+ set.Clear();
+ Assert.AreEqual(0, set.Count);
+ }
+
+ [Test]
+ public void TestIsSupersetOf()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestIsSupersetOf), GetCollectionConfiguration());
+ set.UnionWith(new[] { 1, 2, 3 });
+
+ Assert.IsTrue(set.IsSupersetOf(new[] { 3, 2, 1 }));
+ Assert.IsTrue(set.IsSupersetOf(new[] { 3, 2 }));
+ Assert.IsFalse(set.IsSupersetOf(new[] { 3, 0 }));
+ }
+
+ [Test]
+ public void TestIsProperSupersetOf()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestIsProperSupersetOf), GetCollectionConfiguration());
+ set.UnionWith(new[] { 1, 2, 3 });
+
+ Assert.IsFalse(set.IsProperSupersetOf(new[] { 3, 2, 1 }));
+ Assert.IsTrue(set.IsProperSupersetOf(new[] { 3, 2 }));
+ Assert.IsTrue(set.IsProperSupersetOf(new[] { 1 }));
+ Assert.IsFalse(set.IsProperSupersetOf(new[] { 3, 0 }));
+ }
+
+ [Test]
+ public void TestUnionWith()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestUnionWith), GetCollectionConfiguration());
+ set.UnionWith(new[] { 1 });
+ set.UnionWith(new[] { 1, 2 });
+ set.UnionWith(new[] { 0, 3 });
+
+ CollectionAssert.AreEquivalent(new[] { 0, 1, 2, 3 }, set);
+ }
+
+ [Test]
+ public void TestSetEquals()
+ {
+ var set = Client.GetIgniteSet<int>(nameof(TestSetEquals), GetCollectionConfiguration());
+ set.UnionWith(new[] { 1, 2, 3 });
+
+ Assert.IsTrue(set.SetEquals(new[] { 1, 2, 3 }));
+ Assert.IsTrue(set.SetEquals(new[] { 3, 1, 2 }));
+ Assert.IsFalse(set.SetEquals(new[] { 1, 2 }));
+ Assert.IsFalse(set.SetEquals(new[] { 1, 2, 4 }));
+ Assert.IsFalse(set.SetEquals(new[] { 1, 2, 3, 4 }));
+ }
+
+ protected virtual CollectionClientConfiguration GetCollectionConfiguration() =>
+ new CollectionClientConfiguration();
+
+ private class CustomClass
+ {
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+ }
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/DataStructures/IgniteSetClientTestsColocated.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/DataStructures/IgniteSetClientTestsColocated.cs
new file mode 100644
index 00000000000..4076c8d9e17
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/DataStructures/IgniteSetClientTestsColocated.cs
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Tests.Client.DataStructures
+{
+ using Apache.Ignite.Core.Client.DataStructures;
+ using NUnit.Framework;
+
+ /// <summary>
+ /// Tests for <see cref="IIgniteSetClient{T}"/> in <see cref="CollectionClientConfiguration.Colocated"/> mode.
+ /// </summary>
+ [TestFixture]
+ public class IgniteSetClientTestsColocated : IgniteSetClientTests
+ {
+ protected override CollectionClientConfiguration GetCollectionConfiguration()
+ {
+ var cfg = base.GetCollectionConfiguration();
+ cfg.Colocated = true;
+
+ return cfg;
+ }
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Client/DataStructures/CollectionClientConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Client/DataStructures/CollectionClientConfiguration.cs
new file mode 100644
index 00000000000..b0bb0183875
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Client/DataStructures/CollectionClientConfiguration.cs
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Client.DataStructures
+{
+ using System.ComponentModel;
+ using Apache.Ignite.Core.Cache.Configuration;
+
+ /// <summary>
+ /// Configuration for Ignite collections.
+ /// </summary>
+ public sealed class CollectionClientConfiguration
+ {
+ /// <summary>
+ /// Default value for <see cref="CacheMode"/>.
+ /// </summary>
+ public const CacheMode DefaultCacheMode = CacheMode.Partitioned;
+
+ /// <summary>
+ /// Default value for <see cref="AtomicityMode"/>.
+ /// </summary>
+ public const CacheAtomicityMode DefaultAtomicityMode = CacheAtomicityMode.Atomic;
+
+ /// <summary>
+ /// Gets or sets the number of backup nodes for the underlying cache.
+ /// </summary>
+ public int Backups { get; set; }
+
+ /// <summary>
+ /// Gets or sets the cache mode for the underlying cache.
+ /// </summary>
+ [DefaultValue(DefaultCacheMode)]
+ public CacheMode CacheMode { get; set; } = DefaultCacheMode;
+
+ /// <summary>
+ /// Gets or sets the cache mode for the underlying cache.
+ /// </summary>
+ [DefaultValue(DefaultAtomicityMode)]
+ public CacheAtomicityMode AtomicityMode { get; set; } = DefaultAtomicityMode;
+
+ /// <summary>
+ /// Gets or sets a value indicating whether all set items should be stored on a single node.
+ /// </summary>
+ public bool Colocated { get; set; }
+
+ /// <summary>
+ /// Gets or sets the cache group name.
+ /// </summary>
+ public string GroupName { get; set; }
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Client/DataStructures/IIgniteSetClient.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Client/DataStructures/IIgniteSetClient.cs
new file mode 100644
index 00000000000..fb80ad17ac1
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Client/DataStructures/IIgniteSetClient.cs
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Client.DataStructures
+{
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+
+ /// <summary>
+ /// Distributed set: stores items on one or more Ignite cluster nodes.
+ /// <para />
+ /// Implements most of the methods from <see cref="ISet{T}"/>. The following methods are NOT supported:
+ /// <see cref="ISet{T}.IsSubsetOf"/>, <see cref="ISet{T}.IsProperSubsetOf"/>,
+ /// <see cref="ISet{T}.Overlaps"/>, <see cref="ISet{T}.SymmetricExceptWith"/>.
+ /// <para />
+ /// Set items can be placed on single node (when <see cref="CollectionClientConfiguration.Colocated"/> is true)
+ /// or distributed across grid nodes.
+ /// </summary>
+ /// <typeparam name="T">Item type.</typeparam>
+ [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
+ public interface IIgniteSetClient<T> : ISet<T>
+ {
+ /// <summary>
+ /// Gets the set name.
+ /// </summary>
+ string Name { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether this set is colocated, that is, all items are stored on a single node.
+ /// </summary>
+ bool Colocated { get; }
+
+ /// <summary>
+ /// Gets or sets a value indicating the batch size for multi-item operations such as iteration.
+ /// </summary>
+ int PageSize { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance was removed from the cluster.
+ /// </summary>
+ /// <returns>True if this set was removed; otherwise, false.</returns>
+ bool IsClosed { get; }
+
+ /// <summary>
+ /// Removes this set from the cluster.
+ /// </summary>
+ void Close();
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Client/IIgniteClient.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Client/IIgniteClient.cs
index cdb18dbbf31..9045414efde 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Client/IIgniteClient.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Client/IIgniteClient.cs
@@ -221,5 +221,15 @@ namespace Apache.Ignite.Core.Client
AtomicClientConfiguration configuration,
long initialValue,
bool create);
+
+ /// <summary>
+ /// Gets or creates an Ignite set with the specified name.
+ /// </summary>
+ /// <param name="name">Name.</param>
+ /// <param name="configuration">Configuration. When null, gets and existing set by name;
+ /// otherwise, creates a new set with the specified configuration.</param>
+ /// <typeparam name="T">Element type.</typeparam>
+ /// <returns>Ignite set.</returns>
+ IIgniteSetClient<T> GetIgniteSet<T>(string name, CollectionClientConfiguration configuration);
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientOp.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientOp.cs
index f19237c4638..b97c2c5e05b 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientOp.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/ClientOp.cs
@@ -109,6 +109,21 @@ namespace Apache.Ignite.Core.Impl.Client
AtomicLongValueAddAndGet = 9004,
AtomicLongValueGetAndSet = 9005,
AtomicLongValueCompareAndSet = 9006,
- AtomicLongValueCompareAndSetAndGet = 9007
+ AtomicLongValueCompareAndSetAndGet = 9007,
+
+ SetGetOrCreate = 9010,
+ SetClose = 9011,
+ SetExists = 9012,
+ SetValueAdd = 9013,
+ SetValueAddAll = 9014,
+ SetValueRemove = 9015,
+ SetValueRemoveAll = 9016,
+ SetValueContains = 9017,
+ SetValueContainsAll = 9018,
+ SetValueRetainAll = 9019,
+ SetSize = 9020,
+ SetClear = 9021,
+ SetIteratorStart = 9022,
+ SetIteratorGetPage = 9023
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/DataStructures/IgniteSetClient.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/DataStructures/IgniteSetClient.cs
new file mode 100644
index 00000000000..ff044cd55fb
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/DataStructures/IgniteSetClient.cs
@@ -0,0 +1,293 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Impl.Client.DataStructures
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO;
+ using Apache.Ignite.Core.Client.DataStructures;
+ using Apache.Ignite.Core.Impl.Binary;
+ using Apache.Ignite.Core.Impl.Common;
+ using BinaryWriter = Apache.Ignite.Core.Impl.Binary.BinaryWriter;
+
+ /// <summary>
+ /// Client set.
+ /// </summary>
+ /// <typeparam name="T">Element type.</typeparam>
+ internal sealed class IgniteSetClient<T> : IIgniteSetClient<T>
+ {
+ /** */
+ private readonly ClientFailoverSocket _socket;
+
+ /** */
+ private readonly int _cacheId;
+
+ /** */
+ private readonly object _nameHash;
+
+ /** */
+ private int _pageSize = 1024;
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="IgniteSetClient{T}"/> class,
+ /// </summary>
+ /// <param name="socket">Socket.</param>
+ /// <param name="name">Set name.</param>
+ /// <param name="colocated">Colocated flag.</param>
+ /// <param name="cacheId">Cache id.</param>
+ public IgniteSetClient(ClientFailoverSocket socket, string name, bool colocated, int cacheId)
+ {
+ Debug.Assert(socket != null);
+ Debug.Assert(name != null);
+
+ _socket = socket;
+ _cacheId = cacheId;
+ _nameHash = BinaryUtils.GetStringHashCode(name);
+
+ Name = name;
+ Colocated = colocated;
+ }
+
+ /** <inheritdoc /> */
+ public IEnumerator<T> GetEnumerator()
+ {
+ if (Colocated)
+ {
+ return _socket.DoOutInOpAffinity(
+ ClientOp.SetIteratorStart,
+ ctx => WriteIteratorStart(ctx),
+ ctx => new IgniteSetClientEnumerator<T>(ctx, PageSize),
+ _cacheId,
+ _nameHash);
+ }
+
+ return _socket.DoOutInOp(
+ ClientOp.SetIteratorStart,
+ ctx => WriteIteratorStart(ctx),
+ ctx => new IgniteSetClientEnumerator<T>(ctx, PageSize));
+ }
+
+ /** <inheritdoc /> */
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ /** <inheritdoc /> */
+ void ICollection<T>.Add(T item) => AddIfNotPresent(item);
+
+ /** <inheritdoc /> */
+ public void ExceptWith(IEnumerable<T> other) => MultiKeyOp(ClientOp.SetValueRemoveAll, other, out _);
+
+ /** <inheritdoc /> */
+ public void IntersectWith(IEnumerable<T> other) => MultiKeyOp(ClientOp.SetValueRetainAll, other, out _);
+
+ /** <inheritdoc /> */
+ public bool IsProperSubsetOf(IEnumerable<T> other) => throw new NotSupportedException();
+
+ /** <inheritdoc /> */
+ public bool IsProperSupersetOf(IEnumerable<T> other) =>
+ // If B is a proper superset of A, then all elements of A are in B
+ // but B contains at least one element that is not in A.
+ MultiKeyOp(ClientOp.SetValueContainsAll, other, out var otherCount) && Count > otherCount;
+
+ /** <inheritdoc /> */
+ public bool IsSubsetOf(IEnumerable<T> other) => throw new NotSupportedException();
+
+ /** <inheritdoc /> */
+ public bool IsSupersetOf(IEnumerable<T> other) => MultiKeyOp(ClientOp.SetValueContainsAll, other, out _);
+
+ /** <inheritdoc /> */
+ public bool Overlaps(IEnumerable<T> other) => throw new NotSupportedException();
+
+ /** <inheritdoc /> */
+ public bool SetEquals(IEnumerable<T> other) =>
+ MultiKeyOp(ClientOp.SetValueContainsAll, other, out var otherCount) && Count == otherCount;
+
+ /** <inheritdoc /> */
+ public void SymmetricExceptWith(IEnumerable<T> other) => throw new NotSupportedException();
+
+ /** <inheritdoc /> */
+ public void UnionWith(IEnumerable<T> other) => MultiKeyOp(ClientOp.SetValueAddAll, other, out _);
+
+ /** <inheritdoc /> */
+ bool ISet<T>.Add(T item) => AddIfNotPresent(item);
+
+ /** <inheritdoc /> */
+ public void Clear() => Op<object>(ClientOp.SetClear);
+
+ /** <inheritdoc /> */
+ public bool Contains(T item) => SingleKeyOp(ClientOp.SetValueContains, item);
+
+ /** <inheritdoc /> */
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ IgniteArgumentCheck.NotNull(array, nameof(array));
+
+ if (arrayIndex < 0)
+ {
+ // Here and below - same exception text as HashSet uses.
+ throw new ArgumentOutOfRangeException(
+ paramName: nameof(arrayIndex),
+ actualValue: arrayIndex,
+ message: "Non-negative number required.");
+ }
+
+ foreach (var item in this)
+ {
+ if (arrayIndex >= array.Length)
+ {
+ throw new ArgumentException(
+ message: "Destination array is not long enough to copy all the items in the collection. " +
+ "Check array index and length.",
+ paramName: nameof(array));
+ }
+
+ array[arrayIndex++] = item;
+ }
+ }
+
+ /** <inheritdoc /> */
+ public bool Remove(T item) => SingleKeyOp(ClientOp.SetValueRemove, item);
+
+ /** <inheritdoc /> */
+ public int Count => Op(ClientOp.SetSize, null, r => r.Stream.ReadInt());
+
+ /** <inheritdoc /> */
+ public bool IsReadOnly => false;
+
+ /** <inheritdoc /> */
+ public string Name { get; }
+
+ /** <inheritdoc /> */
+ public bool Colocated { get; }
+
+ /** <inheritdoc /> */
+ public int PageSize
+ {
+ get => _pageSize;
+ set => _pageSize = value > 0 ? value : throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ /** <inheritdoc /> */
+ public bool IsClosed => Op(ClientOp.SetExists, null, r => !r.Stream.ReadBool());
+
+ /** <inheritdoc /> */
+ public void Close() => Op<object>(ClientOp.SetClose);
+
+ private bool AddIfNotPresent(T item) => SingleKeyOp(ClientOp.SetValueAdd, item);
+
+ private TRes Op<TRes>(
+ ClientOp op,
+ Action<BinaryWriter> writeAction = null,
+ Func<ClientResponseContext, TRes> readFunc = null) =>
+ _socket.DoOutInOp(op, ctx =>
+ {
+ WriteIdentity(ctx.Writer);
+ writeAction?.Invoke(ctx.Writer);
+ }, readFunc);
+
+ private bool SingleKeyOp(ClientOp op, T item)
+ {
+ IgniteArgumentCheck.NotNull(item, nameof(item));
+
+ return _socket.DoOutInOpAffinity(
+ op,
+ ctx =>
+ {
+ var w = ctx.Writer;
+
+ WriteIdentity(w);
+ w.WriteBoolean(true); // ServerKeepBinary
+ w.WriteObject(item);
+ },
+ ctx => ctx.Stream.ReadBool(),
+ _cacheId,
+ GetAffinityKey(item));
+ }
+
+ private bool MultiKeyOp(ClientOp op, IEnumerable<T> items, out int itemsCount)
+ {
+ IgniteArgumentCheck.NotNull(items, nameof(items));
+
+ using var enumerator = items.GetEnumerator();
+
+ if (!enumerator.MoveNext())
+ {
+ itemsCount = 0;
+ return false;
+ }
+
+ var first = enumerator.Current;
+ var affKey = GetAffinityKey(first);
+ var count = 1;
+
+ var res = _socket.DoOutInOpAffinity(
+ op,
+ ctx =>
+ {
+ var w = ctx.Writer;
+
+ WriteIdentity(w);
+ w.WriteBoolean(true); // ServerKeepBinary.
+
+ var countPos = w.Stream.Position;
+ w.WriteInt(0);
+
+ w.WriteObjectDetached(first);
+
+ // ReSharper disable once AccessToDisposedClosure
+ while (enumerator.MoveNext())
+ {
+ // ReSharper disable once AccessToDisposedClosure
+ w.WriteObjectDetached(enumerator.Current);
+ count++;
+ }
+
+ var endPos = w.Stream.Position;
+ w.Stream.Seek(countPos, SeekOrigin.Begin);
+ w.Stream.WriteInt(count);
+ w.Stream.Seek(endPos, SeekOrigin.Begin);
+ },
+ ctx => ctx.Reader.ReadBoolean(),
+ _cacheId,
+ affKey);
+
+ itemsCount = count;
+ return res;
+ }
+
+ private void WriteIdentity(BinaryWriter w)
+ {
+ w.WriteString(Name);
+ w.WriteInt(_cacheId);
+ w.WriteBoolean(Colocated);
+ }
+
+ private object GetAffinityKey(T key)
+ {
+ // See ClientIgniteSetImpl#affinityKey in Java for details.
+ return Colocated ? _nameHash : key;
+ }
+
+ private void WriteIteratorStart(ClientRequestContext ctx)
+ {
+ WriteIdentity(ctx.Writer);
+ ctx.Writer.WriteInt(_pageSize);
+ }
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/DataStructures/IgniteSetClientEnumerator.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/DataStructures/IgniteSetClientEnumerator.cs
new file mode 100644
index 00000000000..51de66e4574
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/DataStructures/IgniteSetClientEnumerator.cs
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Core.Impl.Client.DataStructures
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using Apache.Ignite.Core.Binary;
+
+ /// <summary>
+ /// Enumerator for <see cref="IgniteSetClient{T}"/>.
+ /// </summary>
+ internal sealed class IgniteSetClientEnumerator<T> : IEnumerator<T>
+ {
+ /** */
+ private readonly ClientSocket _socket;
+
+ /** */
+ private readonly int _pageSize;
+
+ /** */
+ private long? _resourceId;
+
+ /** */
+ private List<T> _page;
+
+ /** */
+ private int _pos = -1;
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="IgniteSetClientEnumerator{T}"/> class.
+ /// </summary>
+ /// <param name="ctx">Response context.</param>
+ /// <param name="pageSize">Page size.</param>
+ public IgniteSetClientEnumerator(ClientResponseContext ctx, int pageSize)
+ {
+ _page = ReadPage(ctx.Reader);
+
+ var hasNext = ctx.Reader.ReadBoolean();
+
+ _resourceId = hasNext ? ctx.Reader.ReadLong() : (long?)null;
+ _socket = ctx.Socket;
+ _pageSize = pageSize;
+ }
+
+ /** <inheritdoc /> */
+ public bool MoveNext()
+ {
+ if (_pos < _page.Count - 1)
+ {
+ _pos++;
+ return true;
+ }
+
+ if (_resourceId == null)
+ {
+ return false;
+ }
+
+ _socket.DoOutInOp<object>(ClientOp.SetIteratorGetPage,
+ ctx =>
+ {
+ ctx.Stream.WriteLong(_resourceId.Value);
+ ctx.Stream.WriteInt(_pageSize);
+ },
+ ctx =>
+ {
+ _page = ReadPage(ctx.Reader);
+
+ var hasNext = ctx.Reader.ReadBoolean();
+
+ if (!hasNext)
+ {
+ _resourceId = null;
+ }
+
+ return null;
+ });
+
+ _pos = 0;
+ return true;
+ }
+
+ /** <inheritdoc /> */
+ public void Reset() => throw new NotSupportedException();
+
+ /** <inheritdoc /> */
+ public T Current
+ {
+ get
+ {
+ if (_pos == -1)
+ {
+ throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
+ }
+
+ if (_pos >= _page.Count)
+ {
+ throw new InvalidOperationException("Enumeration already finished.");
+ }
+
+ return _page[_pos];
+ }
+ }
+
+ /** <inheritdoc /> */
+ object IEnumerator.Current => Current;
+
+ /** <inheritdoc /> */
+ public void Dispose()
+ {
+ if (_resourceId == null)
+ {
+ return;
+ }
+
+ _socket.DoOutInOp<object>(ClientOp.ResourceClose, ctx => ctx.Stream.WriteLong(_resourceId.Value), null);
+
+ _resourceId = null;
+ _pos = int.MaxValue;
+ }
+
+ private static List<T> ReadPage(IBinaryRawReader r)
+ {
+ var size = r.ReadInt();
+ var res = new List<T>(size);
+
+ for (int i = 0; i < size; i++)
+ {
+ res.Add(r.ReadObject<T>());
+ }
+
+ return res;
+ }
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/IgniteClient.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/IgniteClient.cs
index 728458c7d40..635d07f355e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/IgniteClient.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Client/IgniteClient.cs
@@ -359,6 +359,45 @@ namespace Apache.Ignite.Core.Impl.Client
return res;
}
+ /** <inheritDoc /> */
+ public IIgniteSetClient<T> GetIgniteSet<T>(string name, CollectionClientConfiguration configuration)
+ {
+ IgniteArgumentCheck.NotNullOrEmpty(name, "name");
+
+ return _socket.DoOutInOp(ClientOp.SetGetOrCreate, ctx =>
+ {
+ var w = ctx.Writer;
+
+ w.WriteString(name);
+
+ if (configuration != null)
+ {
+ w.WriteBoolean(true);
+ w.WriteByte((byte)configuration.AtomicityMode);
+ w.WriteByte((byte)configuration.CacheMode);
+ w.WriteInt(configuration.Backups);
+ w.WriteString(configuration.GroupName);
+ w.WriteBoolean(configuration.Colocated);
+ }
+ else
+ {
+ w.WriteBoolean(false);
+ }
+
+ }, ctx =>
+ {
+ if (!ctx.Reader.ReadBoolean())
+ {
+ return null;
+ }
+
+ var colocated = ctx.Reader.ReadBoolean();
+ var cacheId = ctx.Reader.ReadInt();
+
+ return new IgniteSetClient<T>(_socket, name, colocated, cacheId);
+ });
+ }
+
/** <inheritDoc /> */
public IBinaryProcessor BinaryProcessor
{