Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ private TResult ExecuteCommand<TResult>(
SqlLog.Info(nameof(Strings.LogSessionXQueryY), session.ToStringSafely(), command.ToHumanReadableString());
}

session?.Transaction?.CheckForTimeout(command);
session?.Events.NotifyDbCommandExecuting(command);

TResult result;
Expand All @@ -462,6 +463,7 @@ private async Task<TResult> ExecuteCommandAsync<TResult>(Session session,
}

cancellationToken.ThrowIfCancellationRequested();
session?.Transaction?.CheckForTimeout(command);
session?.Events.NotifyDbCommandExecuting(command);

TResult result;
Expand Down
90 changes: 44 additions & 46 deletions Orm/Xtensive.Orm/Orm/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
Expand All @@ -28,12 +29,7 @@ public sealed partial class Transaction : IHasExtensions
/// Gets the current <see cref="Transaction"/> object
/// using <see cref="Session"/>.<see cref="Orm.Session.Current"/>.
/// </summary>
public static Transaction Current {
get {
var session = Session.Current;
return session?.Transaction;
}
}
public static Transaction Current => Session.Current?.Transaction;

/// <summary>
/// Gets the current <see cref="Transaction"/>,
Expand All @@ -44,16 +40,8 @@ public static Transaction Current {
/// <exception cref="InvalidOperationException">
/// <see cref="Transaction.Current"/> <see cref="Transaction"/> is <see langword="null" />.
/// </exception>
public static Transaction Demand()
{
var current = Current;
if (current == null) {
throw new InvalidOperationException(
Strings.ExActiveTransactionIsRequiredForThisOperationUseSessionOpenTransactionToOpenIt);
}

return current;
}
public static Transaction Demand() =>
Current ?? throw new InvalidOperationException(Strings.ExActiveTransactionIsRequiredForThisOperationUseSessionOpenTransactionToOpenIt);

/// <summary>
/// Checks whether a transaction exists or not in the provided session.
Expand All @@ -68,58 +56,70 @@ public static void Require(Session session)

#endregion

private readonly List<StateLifetimeToken> lifetimeTokens;
private readonly List<StateLifetimeToken> lifetimeTokens = new(1);

private ExtensionCollection extensions;
private Transaction inner;

/// <summary>
/// Gets a value indicating whether this instance is automatic transaction.
/// </summary>
public bool IsAutomatic { get; private set; }
public bool IsAutomatic { get; }

/// <summary>
/// Gets a value indicating whether this instance is
/// transaction running locally.
/// </summary>
public bool IsDisconnected { get; private set; }

public bool IsDisconnected { get; }

private Guid? guid;
/// <summary>
/// Gets the unique identifier of this transaction.
/// Nested transactions have the same <see cref="Guid"/>
/// as their outermost.
/// </summary>
public Guid Guid { get; private set; }
public Guid Guid => Outer?.Guid ?? (guid ??= Guid.NewGuid());

/// <summary>
/// Gets the session this transaction is bound to.
/// </summary>
public Session Session { get; private set; }
public Session Session { get; }

/// <summary>
/// Gets the isolation level.
/// </summary>
public IsolationLevel IsolationLevel { get; private set; }
public IsolationLevel IsolationLevel { get; }

/// <summary>
/// Gets the state of the transaction.
/// </summary>
public TransactionState State { get; private set; }
public TransactionState State { get; private set; } = TransactionState.NotActivated;

/// <summary>
/// Gets the outer transaction.
/// </summary>
public Transaction Outer { get; private set; }
public Transaction Outer { get; }

/// <summary>
/// Gets the outermost transaction.
/// </summary>
public Transaction Outermost { get; private set; }
public Transaction Outermost { get; }

/// <summary>
/// Gets the start time of this transaction.
/// </summary>
public DateTime TimeStamp { get; private set; }
public DateTime TimeStamp { get; } = DateTime.UtcNow;

private TimeSpan? timeout;
/// <summary>
/// Gets or sets Transaction timeout
/// </summary>
public TimeSpan? Timeout {
get => timeout;
set => timeout = IsNested
? throw new InvalidOperationException(Strings.ExNestedTransactionTimeout)
: value;
}

/// <summary>
/// Gets a value indicating whether this transaction is a nested transaction.
Expand All @@ -129,7 +129,7 @@ public static void Require(Session session)
/// <summary>
/// Gets <see cref="StateLifetimeToken"/> associated with this transaction.
/// </summary>
public StateLifetimeToken LifetimeToken { get; private set; }
public StateLifetimeToken LifetimeToken { get; private set; } = new();

#region IHasExtensions Members

Expand All @@ -138,7 +138,7 @@ public static void Require(Session session)

#endregion

internal string SavepointName { get; private set; }
internal string SavepointName { get; }

/// <summary>
/// Indicates whether changes made in this transaction are visible "as is"
Expand Down Expand Up @@ -278,34 +278,32 @@ private void ClearLifetimeTokens()
LifetimeToken = null;
}

internal void CheckForTimeout(DbCommand command)
{
if (Timeout is not null) {
var remain = TimeStamp + Timeout.Value - DateTime.UtcNow;
command.CommandTimeout = remain.Ticks > 0
? Math.Max(1, (int) remain.TotalSeconds)
: throw new TimeoutException(String.Format(Strings.ExTransactionTimeout, Timeout));
}
}

#endregion


// Constructors

internal Transaction(Session session, IsolationLevel isolationLevel, bool isAutomatic)
: this(session, isolationLevel, isAutomatic, null, null)
{
}
// Constructors

internal Transaction(Session session, IsolationLevel isolationLevel, bool isAutomatic, Transaction outer,
string savepointName)
internal Transaction(Session session, IsolationLevel isolationLevel, bool isAutomatic, Transaction outer = null,
string savepointName = null)
{
lifetimeTokens = new List<StateLifetimeToken>();

Guid = Guid.NewGuid();
State = TransactionState.NotActivated;
Session = session;
IsolationLevel = isolationLevel;
IsAutomatic = isAutomatic;
IsDisconnected = session.IsDisconnected;
TimeStamp = DateTime.UtcNow;
LifetimeToken = new StateLifetimeToken();
lifetimeTokens.Add(LifetimeToken);

if (outer != null) {
Outer = outer;
Guid = outer.Guid;
Outermost = outer.Outermost;
SavepointName = savepointName;
}
Expand All @@ -314,4 +312,4 @@ internal Transaction(Session session, IsolationLevel isolationLevel, bool isAuto
}
}
}
}
}
32 changes: 25 additions & 7 deletions Orm/Xtensive.Orm/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Orm/Xtensive.Orm/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2603,4 +2603,10 @@ Error: {1}</value>
<data name="ExMaxNumberOfConditionsShouldBeBetweenXAndY" xml:space="preserve">
<value>DomainConfiguration.MaxNumberOfConditions should be between {0} and {1} (included).</value>
</data>
<data name="ExNestedTransactionTimeout" xml:space="preserve">
<value>Nested transaction cannot have timeout</value>
</data>
<data name="ExTransactionTimeout" xml:space="preserve">
<value>Transaction is longer than {0}</value>
</data>
</root>