You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by fl...@apache.org on 2021/10/13 09:32:34 UTC

[tinkerpop] branch TINKERPOP-2556 updated (a6ee742 -> b406c69)

This is an automated email from the ASF dual-hosted git repository.

florianhockmann pushed a change to branch TINKERPOP-2556
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git.


 discard a6ee742  TINKERPOP-2556 Add tx() support for .NET
     add d276458  Fixed spelling CTR
     add 56575f3  Merge branch '3.4-dev' into 3.5-dev
     add 724dbbf  TINKERPOP-2615 Added null tests around cyclic/simplePath CTR
     new b406c69  TINKERPOP-2556 Add tx() support for .NET

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (a6ee742)
            \
             N -- N -- N   refs/heads/TINKERPOP-2556 (b406c69)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 docs/src/upgrade/release-3.4.x.asciidoc            |  2 +-
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |  2 ++
 .../gremlin-javascript/test/cucumber/gremlin.js    |  5 ++++-
 gremlin-python/src/main/python/radish/gremlin.py   |  2 ++
 gremlin-test/features/filter/CyclicPath.feature    | 22 ++++++++++++++++++++++
 gremlin-test/features/filter/SimplePath.feature    | 16 ++++++++++++++++
 6 files changed, 47 insertions(+), 2 deletions(-)

[tinkerpop] 01/01: TINKERPOP-2556 Add tx() support for .NET

Posted by fl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

florianhockmann pushed a commit to branch TINKERPOP-2556
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit b406c6994a5bcc3f457518b17c3824c40972959a
Author: Florian Hockmann <fh...@florian-hockmann.de>
AuthorDate: Wed Oct 13 11:29:53 2021 +0200

    TINKERPOP-2556 Add tx() support for .NET
---
 .github/workflows/build-test.yml                   |   2 +-
 .../Driver/Remote/DriverRemoteConnection.cs        |  35 +++++-
 .../Driver/Remote/DriverRemoteTransaction.cs       |  62 ++++++++++
 gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj  |   5 +-
 .../Process/Remote/IRemoteConnection.cs            |   6 +
 .../src/Gremlin.Net/Process/Traversal/Bytecode.cs  |   2 +-
 .../IRemoteConnection.cs => Traversal/GraphOp.cs}  |  26 ++--
 .../Process/Traversal/GraphTraversalSource.cs      |  32 ++++-
 .../ITransaction.cs}                               |  17 +--
 .../GraphTraversalSourceTests.cs                   |   4 +-
 .../DriverRemoteConnection/GraphTraversalTests.cs  |   2 +-
 .../GraphTraversalTransactionTests.cs              | 107 ++++++++++++++++
 .../Driver/Remote/DriverRemoteTransactionTests.cs  |  57 +++++++++
 gremlin-dotnet/test/pom.xml                        | 137 +++++++++++++++++++++
 14 files changed, 451 insertions(+), 43 deletions(-)

diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 74e912e..7e25b0f 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -185,4 +185,4 @@ jobs:
           touch gremlin-dotnet/src/.glv
           touch gremlin-dotnet/test/.glv
           mvn clean install -pl -:gremlin-javascript,-:gremlin-python,-:gremlint -q -DskipTests -Dci
-          mvn verify -pl :gremlin-dotnet,:gremlin-dotnet-tests -P gremlin-dotnet
\ No newline at end of file
+          mvn verify -pl :gremlin-dotnet,:gremlin-dotnet-tests -P gremlin-dotnet -DincludeNeo4j
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs
index 006cf8a..63649a1 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs
@@ -25,10 +25,10 @@ using System;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using Gremlin.Net.Driver.Messages;
-using Gremlin.Net.Driver;
 using Gremlin.Net.Process.Remote;
 using Gremlin.Net.Process.Traversal;
 using Gremlin.Net.Process.Traversal.Strategy.Decoration;
+using Gremlin.Net.Structure;
 
 namespace Gremlin.Net.Driver.Remote
 {
@@ -49,6 +49,10 @@ namespace Gremlin.Net.Driver.Remote
                     {Tokens.ArgsEvalTimeout, "scriptEvaluationTimeout", Tokens.ArgsBatchSize, 
                      Tokens.RequestId, Tokens.ArgsUserAgent};
 
+        private readonly string _sessionId;
+        private string Processor => IsSessionBound ? Tokens.ProcessorSession : Tokens.ProcessorTraversal;
+        public bool IsSessionBound => _sessionId != null;
+
         /// <summary>
         ///     Initializes a new <see cref="IRemoteConnection" /> using "g" as the default remote TraversalSource name.
         /// </summary>
@@ -91,6 +95,12 @@ namespace Gremlin.Net.Driver.Remote
             _traversalSource = traversalSource ?? throw new ArgumentNullException(nameof(traversalSource));
         }
 
+        private DriverRemoteConnection(IGremlinClient client, string traversalSource, Guid sessionId)
+            : this(client, traversalSource)
+        {
+            _sessionId = sessionId.ToString();
+        }
+
         /// <summary>
         ///     Submits <see cref="Bytecode" /> for evaluation to a remote Gremlin Server.
         /// </summary>
@@ -107,11 +117,16 @@ namespace Gremlin.Net.Driver.Remote
         {
             var requestMsg =
                 RequestMessage.Build(Tokens.OpsBytecode)
-                    .Processor(Tokens.ProcessorTraversal)
+                    .Processor(Processor)
                     .OverrideRequestId(requestid)
                     .AddArgument(Tokens.ArgsGremlin, bytecode)
                     .AddArgument(Tokens.ArgsAliases, new Dictionary<string, string> {{"g", _traversalSource}});
 
+            if (IsSessionBound)
+            {
+                requestMsg.AddArgument(Tokens.ArgsSession, _sessionId);
+            }
+
             var optionsStrategyInst = bytecode.SourceInstructions.Find(
                 s => s.OperatorName == "withStrategies" && s.Arguments[0] is OptionsStrategy);
             if (optionsStrategyInst != null)
@@ -128,6 +143,22 @@ namespace Gremlin.Net.Driver.Remote
             
             return await _client.SubmitAsync<Traverser>(requestMsg.Create()).ConfigureAwait(false);
         }
+        
+        public ITransaction Tx(GraphTraversalSource g)
+        {
+            var session = new DriverRemoteConnection(_client, _traversalSource, Guid.NewGuid());
+            return new DriverRemoteTransaction(session, g);
+        }
+
+        public async Task CommitAsync()
+        {
+            await SubmitAsync<object, object>(GraphOp.Commit).ConfigureAwait(false);
+        }
+        
+        public async Task RollbackAsync()
+        {
+            await SubmitAsync<object, object>(GraphOp.Rollback).ConfigureAwait(false);
+        }
 
         /// <inheritdoc />
         public void Dispose()
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteTransaction.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteTransaction.cs
new file mode 100644
index 0000000..1659bb4
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteTransaction.cs
@@ -0,0 +1,62 @@
+#region License
+
+/*
+ * 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.
+ */
+
+#endregion
+
+using System;
+using System.Threading.Tasks;
+using Gremlin.Net.Process.Traversal;
+using Gremlin.Net.Structure;
+
+namespace Gremlin.Net.Driver.Remote
+{
+    public class DriverRemoteTransaction : ITransaction
+    {
+        private readonly DriverRemoteConnection _sessionBasedConnection;
+        private GraphTraversalSource _g;
+
+        public DriverRemoteTransaction(DriverRemoteConnection connection, GraphTraversalSource g)
+        {
+            _sessionBasedConnection = connection;
+            _g = g;
+        }
+
+        public GraphTraversalSource Begin()
+        {
+            if (_g.IsSessionBound)
+            {
+                throw new InvalidOperationException("Transaction already started on this object");
+            }
+            _g = new GraphTraversalSource(_g.TraversalStrategies, _g.Bytecode, _sessionBasedConnection);
+            return _g;
+        }
+
+        public async Task CommitAsync()
+        {
+            await _sessionBasedConnection.CommitAsync().ConfigureAwait(false);
+        }
+
+        public async Task RollbackAsync()
+        {
+            await _sessionBasedConnection.RollbackAsync().ConfigureAwait(false);
+        }
+    }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj b/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
index 0f963bb..f9e3fc5 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
+++ b/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
@@ -19,8 +19,9 @@ limitations under the License.
 
   <PropertyGroup Label="Build">
     <TargetFramework>netstandard2.0</TargetFramework>
-    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
-    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+<!--    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>-->
+<!--    <GenerateDocumentationFile>true</GenerateDocumentationFile>-->
+    <LangVersion>8</LangVersion>
   </PropertyGroup>
 
   <PropertyGroup Label="Package">
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
index 5393bcb..9502f5d 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
@@ -23,6 +23,7 @@
 
 using System.Threading.Tasks;
 using Gremlin.Net.Process.Traversal;
+using Gremlin.Net.Structure;
 
 namespace Gremlin.Net.Process.Remote
 {
@@ -38,5 +39,10 @@ namespace Gremlin.Net.Process.Remote
         /// <param name="bytecode">The <see cref="Bytecode" /> to send.</param>
         /// <returns>The <see cref="ITraversal" /> with the results and optional side-effects.</returns>
         Task<ITraversal<S, E>> SubmitAsync<S, E>(Bytecode bytecode);
+        
+        ITransaction Tx(GraphTraversalSource graphTraversalSource);
+        bool IsSessionBound { get; }
+        Task CommitAsync();
+        Task RollbackAsync();
     }
 }
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
index 7149e8b..cd53a1c 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
@@ -38,7 +38,7 @@ namespace Gremlin.Net.Process.Traversal
     /// </remarks>
     public class Bytecode
     {
-        private static readonly object[] EmptyArray = new object[0];
+        private static readonly object[] EmptyArray = Array.Empty<object>();
 
         /// <summary>
         ///     Initializes a new instance of the <see cref="Bytecode" /> class.
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphOp.cs
similarity index 55%
copy from gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
copy to gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphOp.cs
index 5393bcb..b938541 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphOp.cs
@@ -21,22 +21,18 @@
 
 #endregion
 
-using System.Threading.Tasks;
-using Gremlin.Net.Process.Traversal;
-
-namespace Gremlin.Net.Process.Remote
+namespace Gremlin.Net.Process.Traversal
 {
-    /// <summary>
-    ///     A simple abstraction of a "connection" to a "server".
-    /// </summary>
-    public interface IRemoteConnection
+    public static class GraphOp
     {
-        /// <summary>
-        ///     Submits <see cref="ITraversal" /> <see cref="Bytecode" /> to a server and returns a
-        ///     <see cref="ITraversal" />.
-        /// </summary>
-        /// <param name="bytecode">The <see cref="Bytecode" /> to send.</param>
-        /// <returns>The <see cref="ITraversal" /> with the results and optional side-effects.</returns>
-        Task<ITraversal<S, E>> SubmitAsync<S, E>(Bytecode bytecode);
+        public static Bytecode Commit { get; } = CreateGraphOp("tx", "commit");
+        public static Bytecode Rollback { get; } = CreateGraphOp("tx", "rollback");
+
+        private static Bytecode CreateGraphOp(string name, object value)
+        {
+            var bytecode = new Bytecode();
+            bytecode.AddSource(name, value);
+            return bytecode;
+        }
     }
 }
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
index c74e826..ba31cd7 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
@@ -24,6 +24,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Gremlin.Net.Driver.Remote;
 using Gremlin.Net.Process.Remote;
 using Gremlin.Net.Process.Traversal.Strategy.Decoration;
 using Gremlin.Net.Structure;
@@ -38,6 +39,10 @@ namespace Gremlin.Net.Process.Traversal
     /// </summary>
     public class GraphTraversalSource
     {
+        private readonly IRemoteConnection _connection;
+
+        public bool IsSessionBound => _connection is { IsSessionBound: true };
+        
         /// <summary>
         ///     Gets or sets the traversal strategies associated with this graph traversal source.
         /// </summary>
@@ -71,6 +76,15 @@ namespace Gremlin.Net.Process.Traversal
             Bytecode = bytecode;
         }
 
+        public GraphTraversalSource(ICollection<ITraversalStrategy> traversalStrategies, Bytecode bytecode,
+            IRemoteConnection connection)
+            : this(traversalStrategies.Where(strategy => strategy.GetType() != typeof(RemoteStrategy)).ToList(),
+                bytecode)
+        {
+            _connection = connection;
+            TraversalStrategies.Add(new RemoteStrategy(connection));
+        }
+
         public GraphTraversalSource With(string key)
         {
             return With(key, true);
@@ -243,12 +257,19 @@ namespace Gremlin.Net.Process.Traversal
         ///     <see cref="GraphTraversal{SType, EType}" />.
         /// </param>
         /// <returns>A <see cref="GraphTraversalSource" /> configured to use the provided <see cref="IRemoteConnection" />.</returns>
-        public GraphTraversalSource WithRemote(IRemoteConnection remoteConnection)
+        public GraphTraversalSource WithRemote(IRemoteConnection remoteConnection) =>
+            new GraphTraversalSource(new List<ITraversalStrategy>(TraversalStrategies),
+                new Bytecode(Bytecode), remoteConnection);
+
+        public ITransaction Tx()
         {
-            var source = new GraphTraversalSource(new List<ITraversalStrategy>(TraversalStrategies),
-                new Bytecode(Bytecode));
-            source.TraversalStrategies.Add(new RemoteStrategy(remoteConnection));
-            return source;
+            // you can't do g.tx().begin().tx() - no child transactions
+            if (IsSessionBound)
+            {
+                throw new InvalidOperationException(
+                    "This GraphTraversalSource is already bound to a transaction - child transactions are not supported");
+            }
+            return _connection.Tx(this);
         }
 
         /// <summary>
@@ -377,7 +398,6 @@ namespace Gremlin.Net.Process.Traversal
                 traversal.Bytecode.AddStep("io", file);
             return traversal;
         }
-
     }
     
 #pragma warning restore 1591
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/ITransaction.cs
similarity index 59%
copy from gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
copy to gremlin-dotnet/src/Gremlin.Net/Structure/ITransaction.cs
index 5393bcb..f3dc77e 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/ITransaction.cs
@@ -24,19 +24,12 @@
 using System.Threading.Tasks;
 using Gremlin.Net.Process.Traversal;
 
-namespace Gremlin.Net.Process.Remote
+namespace Gremlin.Net.Structure
 {
-    /// <summary>
-    ///     A simple abstraction of a "connection" to a "server".
-    /// </summary>
-    public interface IRemoteConnection
+    public interface ITransaction
     {
-        /// <summary>
-        ///     Submits <see cref="ITraversal" /> <see cref="Bytecode" /> to a server and returns a
-        ///     <see cref="ITraversal" />.
-        /// </summary>
-        /// <param name="bytecode">The <see cref="Bytecode" /> to send.</param>
-        /// <returns>The <see cref="ITraversal" /> with the results and optional side-effects.</returns>
-        Task<ITraversal<S, E>> SubmitAsync<S, E>(Bytecode bytecode);
+        GraphTraversalSource Begin();
+        Task CommitAsync();
+        Task RollbackAsync();
     }
 }
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalSourceTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalSourceTests.cs
index 1a35679..713394f 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalSourceTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalSourceTests.cs
@@ -1,4 +1,4 @@
-#region License
+#region License
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -21,10 +21,8 @@
 
 #endregion
 
-using System;
 using System.Collections.Generic;
 using Gremlin.Net.Process.Traversal;
-using Gremlin.Net.Process.Traversal.Strategy.Verification;
 using Xunit;
 
 namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
index 89597a3..b0347bc 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
@@ -247,4 +247,4 @@ namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection
             Assert.Equal(6, count);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTransactionTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTransactionTests.cs
new file mode 100644
index 0000000..fb968af
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTransactionTests.cs
@@ -0,0 +1,107 @@
+#region License
+
+/*
+ * 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.
+ */
+
+#endregion
+
+using System;
+using System.Threading.Tasks;
+using Gremlin.Net.Process.Remote;
+using Gremlin.Net.Process.Traversal;
+using Xunit;
+
+namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection
+{
+    public class GraphTraversalTransactionTests : IDisposable
+    {
+        private readonly IRemoteConnection _connection = new RemoteConnectionFactory().CreateRemoteConnection("gtx");
+
+        [IgnoreIfTransactionsNotSupportedFact]
+        public async Task ShouldSupportRemoteTransactionsCommit()
+        {
+            var g = AnonymousTraversalSource.Traversal().WithRemote(_connection);
+            var tx = g.Tx();
+            var gtx = tx.Begin();
+            await gtx.AddV("person").Property("name", "florian").Promise(t => t.Iterate()).ConfigureAwait(false);
+            await gtx.AddV("person").Property("name", "josh").Promise(t => t.Iterate()).ConfigureAwait(false);
+            
+            // Assert within the transaction
+            var count = await gtx.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
+            Assert.Equal(2, count);
+            
+            // Vertices should not be visible in a different transaction before commiting
+            count = await g.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
+            Assert.Equal(0, count);
+            
+            // Now commit changes to test outside of the transaction
+            await tx.CommitAsync().ConfigureAwait(false);
+
+            count = await g.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
+            Assert.Equal(2, count);
+        }
+        
+        [IgnoreIfTransactionsNotSupportedFact]
+        public async Task ShouldSupportRemoteTransactionsRollback()
+        {
+            var g = AnonymousTraversalSource.Traversal().WithRemote(_connection);
+            var tx = g.Tx();
+            var gtx = tx.Begin();
+            await gtx.AddV("person").Property("name", "florian").Promise(t => t.Iterate()).ConfigureAwait(false);
+            await gtx.AddV("person").Property("name", "josh").Promise(t => t.Iterate()).ConfigureAwait(false);
+            
+            // Assert within the transaction
+            var count = await gtx.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
+            Assert.Equal(2, count);
+            
+            // Now rollback changes to test outside of the transaction
+            await tx.RollbackAsync().ConfigureAwait(false);
+
+            count = await g.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
+            Assert.Equal(0, count);
+            
+            g.V().Count().Next();
+        }
+
+        public void Dispose()
+        {
+            EmptyGraph();
+        }
+
+        private void EmptyGraph()
+        {
+            var g = AnonymousTraversalSource.Traversal().WithRemote(_connection);
+            g.V().Drop().Iterate();
+        }
+    }
+
+    public sealed class IgnoreIfTransactionsNotSupportedFact : FactAttribute
+    {
+        public IgnoreIfTransactionsNotSupportedFact()
+        {
+            if (!TransactionsSupported)
+            {
+                Skip = "Transactions not supported";
+            }
+        }
+
+        private static bool TransactionsSupported =>
+            Convert.ToBoolean(Environment.GetEnvironmentVariable("TEST_TRANSACTIONS"));
+    }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Driver/Remote/DriverRemoteTransactionTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Driver/Remote/DriverRemoteTransactionTests.cs
new file mode 100644
index 0000000..51f2fc3
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Driver/Remote/DriverRemoteTransactionTests.cs
@@ -0,0 +1,57 @@
+#region License
+
+/*
+ * 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.
+ */
+
+#endregion
+
+using System;
+using Gremlin.Net.Driver;
+using Gremlin.Net.Driver.Remote;
+using Gremlin.Net.Process.Traversal;
+using Moq;
+using Xunit;
+
+namespace Gremlin.Net.UnitTest.Driver.Remote
+{
+    public class DriverRemoteTransactionTests
+    {
+        [Fact]
+        public void ShouldNotAllowBeginMoreThanOnce()
+        {
+            var g = AnonymousTraversalSource.Traversal()
+                .WithRemote(new DriverRemoteConnection(Mock.Of<IGremlinClient>()));
+            var tx = g.Tx();
+            tx.Begin();
+
+            Assert.Throws<InvalidOperationException>(() => tx.Begin());
+        }
+        
+        [Fact]
+        public void ShouldNotSupportChildTransactions()
+        {
+            var g = AnonymousTraversalSource.Traversal()
+                .WithRemote(new DriverRemoteConnection(Mock.Of<IGremlinClient>()));
+            var tx = g.Tx();
+            
+            var gtx = tx.Begin();
+            Assert.Throws<InvalidOperationException>(() => gtx.Tx());
+        }
+    }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/pom.xml b/gremlin-dotnet/test/pom.xml
index d110391..f1be8e2 100644
--- a/gremlin-dotnet/test/pom.xml
+++ b/gremlin-dotnet/test/pom.xml
@@ -96,6 +96,16 @@ limitations under the License.
                         <extensions>true</extensions>
                         <configuration>
                             <skip>${skipTests}</skip>
+                            <!--
+                            transaction testing is disabled unless the -DincludeNeo4j flag enables the include-neo4j
+                            maven profile which is a standard profile we use to add neo4j to testing explicitly - for
+                            npm we set this TEST_TRANSACTIONS environment variable that can be accessed in tests to
+                            determine if we skip transaction oriented tests or not. without neo4j we can't test tx()
+                            so this is disabled by default and enabled in the include-neo4j profile below
+                            -->
+                            <environment>
+                                <TEST_TRANSACTIONS>false</TEST_TRANSACTIONS>
+                            </environment>
                         </configuration>
                     </plugin>
                     <plugin>
@@ -113,6 +123,11 @@ limitations under the License.
                                 <version>${project.version}</version>
                             </dependency>
                             <dependency>
+                                <groupId>org.apache.tinkerpop</groupId>
+                                <artifactId>neo4j-gremlin</artifactId>
+                                <version>${project.version}</version>
+                            </dependency>
+                            <dependency>
                                 <groupId>log4j</groupId>
                                 <artifactId>log4j</artifactId>
                                 <version>${log4j.version}</version>
@@ -206,5 +221,127 @@ limitations under the License.
                 </plugins>
             </build>
         </profile>
+        <!--
+          This profile will include neo4j for purposes of transactional testing within Gremlin Server.
+          Tests that require neo4j specifically will be "ignored" if this profile is not turned on.
+        -->
+        <profile>
+            <id>include-neo4j</id>
+            <activation>
+                <activeByDefault>false</activeByDefault>
+                <property>
+                    <name>includeNeo4j</name>
+                </property>
+            </activation>
+            <properties>
+                <packaging.type>dotnet-integration-test</packaging.type>
+            </properties>
+            <build>
+                <plugins>
+                    <!-- with neo4j present we can enable transaction testing -->
+                    <plugin>
+                        <groupId>org.eobjects.build</groupId>
+                        <artifactId>dotnet-maven-plugin</artifactId>
+                        <configuration>
+                            <environment>
+                                <TEST_TRANSACTIONS>true</TEST_TRANSACTIONS>
+                            </environment>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.codehaus.gmavenplus</groupId>
+                        <artifactId>gmavenplus-plugin</artifactId>
+                        <dependencies>
+                            <dependency>
+                                <groupId>org.neo4j</groupId>
+                                <artifactId>neo4j-tinkerpop-api-impl</artifactId>
+                                <version>0.9-3.4.0</version>
+                                <exclusions>
+                                    <exclusion>
+                                        <groupId>org.neo4j</groupId>
+                                        <artifactId>neo4j-kernel</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>org.apache.commons</groupId>
+                                        <artifactId>commons-lang3</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>org.apache.commons</groupId>
+                                        <artifactId>commons-text</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>com.github.ben-manes.caffeine</groupId>
+                                        <artifactId>caffeine</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>org.scala-lang</groupId>
+                                        <artifactId>scala-library</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>org.scala-lang</groupId>
+                                        <artifactId>scala-reflect</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>org.slf4j</groupId>
+                                        <artifactId>slf4j-api</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>org.slf4j</groupId>
+                                        <artifactId>slf4j-nop</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>org.apache.lucene</groupId>
+                                        <artifactId>lucene-core</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>io.dropwizard.metrics</groupId>
+                                        <artifactId>metrics-core</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>io.netty</groupId>
+                                        <artifactId>netty-all</artifactId>
+                                    </exclusion>
+                                    <exclusion>
+                                        <groupId>org.ow2.asm</groupId>
+                                        <artifactId>asm</artifactId>
+                                    </exclusion>
+                                </exclusions>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.scala-lang</groupId>
+                                <artifactId>scala-library</artifactId>
+                                <version>2.11.8</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.scala-lang</groupId>
+                                <artifactId>scala-reflect</artifactId>
+                                <version>2.11.8</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.apache.lucene</groupId>
+                                <artifactId>lucene-core</artifactId>
+                                <version>5.5.0</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>io.dropwizard.metrics</groupId>
+                                <artifactId>metrics-core</artifactId>
+                                <version>4.0.2</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.neo4j</groupId>
+                                <artifactId>neo4j-kernel</artifactId>
+                                <version>3.4.11</version>
+                                <exclusions>
+                                    <exclusion>
+                                        <groupId>io.netty</groupId>
+                                        <artifactId>netty-all</artifactId>
+                                    </exclusion>
+                                </exclusions>
+                            </dependency>
+                        </dependencies>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
     </profiles>
 </project>
\ No newline at end of file