diff --git a/src/Microsoft.EntityFrameworkCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceHiLoValueGenerator.cs b/src/Microsoft.EntityFrameworkCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceHiLoValueGenerator.cs
index a4e23de7e66..7664239f3df 100644
--- a/src/Microsoft.EntityFrameworkCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceHiLoValueGenerator.cs
+++ b/src/Microsoft.EntityFrameworkCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceHiLoValueGenerator.cs
@@ -3,6 +3,8 @@
using System;
using System.Globalization;
+using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage;
@@ -56,6 +58,18 @@ protected override long GetNewLowValue()
typeof(long),
CultureInfo.InvariantCulture);
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected override async Task GetNewLowValueAsync(CancellationToken cancellationToken = default(CancellationToken))
+ => (long)Convert.ChangeType(
+ await _rawSqlCommandBuilder
+ .Build(_sqlGenerator.GenerateNextSequenceValueOperation(_sequence.Name, _sequence.Schema))
+ .ExecuteScalarAsync(_connection, cancellationToken: cancellationToken),
+ typeof(long),
+ CultureInfo.InvariantCulture);
+
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
diff --git a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/IKeyPropagator.cs b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/IKeyPropagator.cs
index 4bda034358f..0a851f0f706 100644
--- a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/IKeyPropagator.cs
+++ b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/IKeyPropagator.cs
@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -17,5 +19,14 @@ public interface IKeyPropagator
/// directly from your code. This API may change or be removed in future releases.
///
void PropagateValue([NotNull] InternalEntityEntry entry, [NotNull] IProperty property);
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ Task PropagateValueAsync(
+ [NotNull] InternalEntityEntry entry,
+ [NotNull] IProperty property,
+ CancellationToken cancellationToken = default(CancellationToken));
}
}
diff --git a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/IValueGenerationManager.cs b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/IValueGenerationManager.cs
index 4155468c23a..f2caad82364 100644
--- a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/IValueGenerationManager.cs
+++ b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/IValueGenerationManager.cs
@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -18,6 +20,14 @@ public interface IValueGenerationManager
///
void Generate([NotNull] InternalEntityEntry entry);
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ Task GenerateAsync(
+ [NotNull] InternalEntityEntry entry,
+ CancellationToken cancellationToken = default(CancellationToken));
+
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
diff --git a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/InternalEntityEntry.cs
index 08d066f7af5..32b80482605 100644
--- a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/InternalEntityEntry.cs
+++ b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/InternalEntityEntry.cs
@@ -8,6 +8,8 @@
using System.Diagnostics;
using System.Linq;
using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -74,6 +76,25 @@ public virtual void SetEntityState(EntityState entityState, bool acceptChanges =
SetEntityState(oldState, entityState, acceptChanges);
}
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual async Task SetEntityStateAsync(
+ EntityState entityState,
+ bool acceptChanges,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var oldState = _stateData.EntityState;
+
+ if (PrepareForAdd(entityState))
+ {
+ await StateManager.ValueGeneration.GenerateAsync(this, cancellationToken);
+ }
+
+ SetEntityState(oldState, entityState, acceptChanges);
+ }
+
private bool PrepareForAdd(EntityState newState)
{
if (newState != EntityState.Added
diff --git a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/KeyPropagator.cs b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/KeyPropagator.cs
index 729923e6a66..4d1189ee1d1 100644
--- a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/KeyPropagator.cs
+++ b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/KeyPropagator.cs
@@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -48,6 +50,29 @@ public virtual void PropagateValue(InternalEntityEntry entry, IProperty property
}
}
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual async Task PropagateValueAsync(
+ InternalEntityEntry entry,
+ IProperty property,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ Debug.Assert(property.IsForeignKey());
+
+ if (!TryPropagateValue(entry, property)
+ && property.IsKey())
+ {
+ var valueGenerator = TryGetValueGenerator(property);
+
+ if (valueGenerator != null)
+ {
+ entry[property] = await valueGenerator.NextAsync(new EntityEntry(entry), cancellationToken);
+ }
+ }
+ }
+
private bool TryPropagateValue(InternalEntityEntry entry, IProperty property)
{
var entityType = entry.EntityType;
diff --git a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/ValueGenerationManager.cs b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/ValueGenerationManager.cs
index d524bdc9e63..a6c43a8222e 100644
--- a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/ValueGenerationManager.cs
+++ b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/ValueGenerationManager.cs
@@ -1,7 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System.Diagnostics;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -10,7 +13,7 @@
namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal
{
///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public class ValueGenerationManager : IValueGenerationManager
@@ -19,7 +22,7 @@ public class ValueGenerationManager : IValueGenerationManager
private readonly IKeyPropagator _keyPropagator;
///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public ValueGenerationManager(
@@ -31,13 +34,67 @@ public ValueGenerationManager(
}
///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public virtual void Generate(InternalEntityEntry entry)
{
var entityEntry = new EntityEntry(entry);
+ foreach (var propertyTuple in FindProperties(entry))
+ {
+ var property = propertyTuple.Item1;
+
+ if (propertyTuple.Item2)
+ {
+ _keyPropagator.PropagateValue(entry, property);
+ }
+ else
+ {
+ var valueGenerator = GetValueGenerator(entry, property);
+
+ SetGeneratedValue(
+ entry,
+ property,
+ valueGenerator.Next(entityEntry),
+ valueGenerator.GeneratesTemporaryValues);
+ }
+ }
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public virtual async Task GenerateAsync(
+ InternalEntityEntry entry,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var entityEntry = new EntityEntry(entry);
+
+ foreach (var propertyTuple in FindProperties(entry))
+ {
+ var property = propertyTuple.Item1;
+
+ if (propertyTuple.Item2)
+ {
+ _keyPropagator.PropagateValue(entry, property);
+ }
+ else
+ {
+ var valueGenerator = GetValueGenerator(entry, property);
+
+ SetGeneratedValue(
+ entry,
+ property,
+ await valueGenerator.NextAsync(entityEntry, cancellationToken),
+ valueGenerator.GeneratesTemporaryValues);
+ }
+ }
+ }
+
+ private IEnumerable> FindProperties(InternalEntityEntry entry)
+ {
foreach (var property in entry.EntityType.GetProperties())
{
var isForeignKey = property.IsForeignKey();
@@ -45,26 +102,18 @@ public virtual void Generate(InternalEntityEntry entry)
if ((property.RequiresValueGenerator || isForeignKey)
&& property.ClrType.IsDefaultValue(entry[property]))
{
- if (isForeignKey)
- {
- _keyPropagator.PropagateValue(entry, property);
- }
- else
- {
- var valueGenerator = _valueGeneratorSelector.Select(property, property.IsKey()
- ? property.DeclaringEntityType
- : entry.EntityType);
-
- Debug.Assert(valueGenerator != null);
-
- SetGeneratedValue(entry, property, valueGenerator.Next(entityEntry), valueGenerator.GeneratesTemporaryValues);
- }
+ yield return Tuple.Create(property, isForeignKey);
}
}
}
+ private ValueGenerator GetValueGenerator(InternalEntityEntry entry, IProperty property)
+ => _valueGeneratorSelector.Select(property, property.IsKey()
+ ? property.DeclaringEntityType
+ : entry.EntityType);
+
///
- /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public virtual bool MayGetTemporaryValue(IProperty property, IEntityType entityType)
diff --git a/src/Microsoft.EntityFrameworkCore/DbContext.cs b/src/Microsoft.EntityFrameworkCore/DbContext.cs
index a496985c914..47e9d0442a7 100644
--- a/src/Microsoft.EntityFrameworkCore/DbContext.cs
+++ b/src/Microsoft.EntityFrameworkCore/DbContext.cs
@@ -104,7 +104,7 @@ private IStateManager StateManager
=> _stateManager
?? (_stateManager = InternalServiceProvider.GetRequiredService());
- internal IAsyncQueryProvider QueryProvider
+ internal IAsyncQueryProvider QueryProvider
=> _queryProvider ?? (_queryProvider = this.GetService());
private IServiceProvider InternalServiceProvider
@@ -440,6 +440,41 @@ private void SetEntityState(InternalEntityEntry entry, EntityState entityState)
public virtual EntityEntry Add([NotNull] TEntity entity) where TEntity : class
=> SetEntityState(Check.NotNull(entity, nameof(entity)), EntityState.Added);
+ ///
+ ///
+ /// Begins tracking the given entity, and any other reachable entities that are
+ /// not already being tracked, in the state such that they will
+ /// be inserted into the database when is called.
+ ///
+ ///
+ /// This method is async only to allow special value generators, such as the one used by
+ /// 'Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.SequenceHiLo',
+ /// to access the database asynchronously. For all other cases the non async method should be used.
+ ///
+ ///
+ /// The type of the entity.
+ /// The entity to add.
+ /// A to observe while waiting for the task to complete.
+ ///
+ /// A task that represents the asynchronous Add operation. The task result contains the
+ /// for the entity. The entry provides access to change tracking
+ /// information and operations for the entity.
+ ///
+ public virtual async Task> AddAsync(
+ [NotNull] TEntity entity,
+ CancellationToken cancellationToken = default(CancellationToken))
+ where TEntity : class
+ {
+ var entry = EntryWithoutDetectChanges(entity);
+
+ await entry.GetInfrastructure().SetEntityStateAsync(
+ EntityState.Added,
+ acceptChanges: true,
+ cancellationToken: cancellationToken);
+
+ return entry;
+ }
+
///
/// Begins tracking the given entity, and any other reachable entities that are
/// not already being tracked, in the state such that no
@@ -544,6 +579,39 @@ private EntityEntry SetEntityState(
public virtual EntityEntry Add([NotNull] object entity)
=> SetEntityState(Check.NotNull(entity, nameof(entity)), EntityState.Added);
+ ///
+ ///
+ /// Begins tracking the given entity, and any other reachable entities that are
+ /// not already being tracked, in the state such that they will
+ /// be inserted into the database when is called.
+ ///
+ ///
+ /// This method is async only to allow special value generators, such as the one used by
+ /// 'Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.SequenceHiLo',
+ /// to access the database asynchronously. For all other cases the non async method should be used.
+ ///
+ ///
+ /// The entity to add.
+ /// A to observe while waiting for the task to complete.
+ ///
+ /// A task that represents the asynchronous Add operation. The task result contains the
+ /// for the entity. The entry provides access to change tracking
+ /// information and operations for the entity.
+ ///
+ public virtual async Task AddAsync(
+ [NotNull] object entity,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var entry = EntryWithoutDetectChanges(entity);
+
+ await entry.GetInfrastructure().SetEntityStateAsync(
+ EntityState.Added,
+ acceptChanges: true,
+ cancellationToken: cancellationToken);
+
+ return entry;
+ }
+
///
/// Begins tracking the given entity, and any other reachable entities that are
/// not already being tracked, in the state such that no
@@ -638,6 +706,23 @@ private EntityEntry SetEntityState(object entity, EntityState entityState)
public virtual void AddRange([NotNull] params object[] entities)
=> AddRange((IEnumerable)entities);
+ ///
+ ///
+ /// Begins tracking the given entity, and any other reachable entities that are
+ /// not already being tracked, in the state such that they will
+ /// be inserted into the database when is called.
+ ///
+ ///
+ /// This method is async only to allow special value generators, such as the one used by
+ /// 'Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.SequenceHiLo',
+ /// to access the database asynchronously. For all other cases the non async method should be used.
+ ///
+ ///
+ /// The entities to add.
+ /// A task that represents the asynchronous operation.
+ public virtual Task AddRangeAsync([NotNull] params object[] entities)
+ => AddRangeAsync((IEnumerable)entities);
+
///
/// Begins tracking the given entities, and any other reachable entities that are
/// not already being tracked, in the state such that no
@@ -702,6 +787,38 @@ private void SetEntityStates(IEnumerable entities, EntityState entitySta
public virtual void AddRange([NotNull] IEnumerable entities)
=> SetEntityStates(Check.NotNull(entities, nameof(entities)), EntityState.Added);
+ ///
+ ///
+ /// Begins tracking the given entity, and any other reachable entities that are
+ /// not already being tracked, in the state such that they will
+ /// be inserted into the database when is called.
+ ///
+ ///
+ /// This method is async only to allow special value generators, such as the one used by
+ /// 'Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.SequenceHiLo',
+ /// to access the database asynchronously. For all other cases the non async method should be used.
+ ///
+ ///
+ /// The entities to add.
+ /// A to observe while waiting for the task to complete.
+ ///
+ /// A task that represents the asynchronous operation.
+ ///
+ public virtual async Task AddRangeAsync(
+ [NotNull] IEnumerable entities,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var stateManager = StateManager;
+
+ foreach (var entity in entities)
+ {
+ await stateManager.GetOrCreateEntry(entity).SetEntityStateAsync(
+ EntityState.Added,
+ acceptChanges: true,
+ cancellationToken: cancellationToken);
+ }
+ }
+
///
/// Begins tracking the given entities, and any other reachable entities that are
/// not already being tracked, in the state such that no
diff --git a/src/Microsoft.EntityFrameworkCore/DbSet`.cs b/src/Microsoft.EntityFrameworkCore/DbSet`.cs
index ebcf07070b6..2234d4edd88 100644
--- a/src/Microsoft.EntityFrameworkCore/DbSet`.cs
+++ b/src/Microsoft.EntityFrameworkCore/DbSet`.cs
@@ -103,6 +103,32 @@ public virtual EntityEntry Add([NotNull] TEntity entity)
throw new NotImplementedException();
}
+ ///
+ ///
+ /// Begins tracking the given entity, and any other reachable entities that are
+ /// not already being tracked, in the state such that they will
+ /// be inserted into the database when is called.
+ ///
+ ///
+ /// This method is async only to allow special value generators, such as the one used by
+ /// 'Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.SequenceHiLo',
+ /// to access the database asynchronously. For all other cases the non async method should be used.
+ ///
+ ///
+ /// The entity to add.
+ /// A to observe while waiting for the task to complete.
+ ///
+ /// A task that represents the asynchronous Add operation. The task result contains the
+ /// for the entity. The entry provides access to change tracking
+ /// information and operations for the entity.
+ ///
+ public virtual Task> AddAsync(
+ [NotNull] TEntity entity,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
///
/// Begins tracking the given entity, and any other reachable entities that are
/// not already being tracked, in the state such that no
@@ -177,6 +203,25 @@ public virtual void AddRange([NotNull] params TEntity[] entities)
throw new NotImplementedException();
}
+ ///
+ ///
+ /// Begins tracking the given entities, and any other reachable entities that are
+ /// not already being tracked, in the state such that they will
+ /// be inserted into the database when is called.
+ ///
+ ///
+ /// This method is async only to allow special value generators, such as the one used by
+ /// 'Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.SequenceHiLo',
+ /// to access the database asynchronously. For all other cases the non async method should be used.
+ ///
+ ///
+ /// The entities to add.
+ /// A task that represents the asynchronous operation.
+ public virtual Task AddRangeAsync([NotNull] params TEntity[] entities)
+ {
+ throw new NotImplementedException();
+ }
+
///
/// Begins tracking the given entities, and any other reachable entities that are
/// not already being tracked, in the state such that no
@@ -239,6 +284,28 @@ public virtual void AddRange([NotNull] IEnumerable entities)
throw new NotImplementedException();
}
+ ///
+ ///
+ /// Begins tracking the given entities, and any other reachable entities that are
+ /// not already being tracked, in the state such that they will
+ /// be inserted into the database when is called.
+ ///
+ ///
+ /// This method is async only to allow special value generators, such as the one used by
+ /// 'Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.SequenceHiLo',
+ /// to access the database asynchronously. For all other cases the non async method should be used.
+ ///
+ ///
+ /// The entities to add.
+ /// A to observe while waiting for the task to complete.
+ /// A task that represents the asynchronous operation.
+ public virtual Task AddRangeAsync(
+ [NotNull] IEnumerable entities,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
///
/// Begins tracking the given entities, and any other reachable entities that are
/// not already being tracked, in the state such that no
diff --git a/src/Microsoft.EntityFrameworkCore/Internal/AsyncLock.cs b/src/Microsoft.EntityFrameworkCore/Internal/AsyncLock.cs
new file mode 100644
index 00000000000..27d12331afa
--- /dev/null
+++ b/src/Microsoft.EntityFrameworkCore/Internal/AsyncLock.cs
@@ -0,0 +1,78 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+
+namespace Microsoft.EntityFrameworkCore.Internal
+{
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public sealed class AsyncLock
+ {
+ private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
+ private readonly Releaser _releaser;
+ private readonly Task _releaserTask;
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public AsyncLock()
+ {
+ _releaser = new Releaser(this);
+ _releaserTask = Task.FromResult(_releaser);
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public Task LockAsync(CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var wait = _semaphore.WaitAsync(cancellationToken);
+
+ return wait.IsCompleted ?
+ _releaserTask :
+ wait.ContinueWith((_, state) => ((AsyncLock)state)._releaser,
+ this, CancellationToken.None,
+ TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public Releaser Lock()
+ {
+ _semaphore.Wait();
+
+ return _releaser;
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public struct Releaser : IDisposable
+ {
+ private readonly AsyncLock _toRelease;
+
+ internal Releaser([NotNull] AsyncLock toRelease)
+ {
+ _toRelease = toRelease;
+ }
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public void Dispose()
+ => _toRelease._semaphore.Release();
+ }
+ }
+}
diff --git a/src/Microsoft.EntityFrameworkCore/Internal/InternalDbSet.cs b/src/Microsoft.EntityFrameworkCore/Internal/InternalDbSet.cs
index fa22449057b..48704ed58ba 100644
--- a/src/Microsoft.EntityFrameworkCore/Internal/InternalDbSet.cs
+++ b/src/Microsoft.EntityFrameworkCore/Internal/InternalDbSet.cs
@@ -48,14 +48,9 @@ public InternalDbSet([NotNull] DbContext context)
}
///
- /// Finds an entity with the given primary key values. If an entity with the given primary key values
- /// is being tracked by the context, then it is returned immediately without making a request to the
- /// database. Otherwise, a query is made to the dataabse for an entity with the given primary key values
- /// and this entity, if found, is attached to the context and returned. If no entity is found, then
- /// null is returned.
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
///
- /// The values of the primary key for the entity to be found.
- /// The entity found, or null.
public override TEntity Find(params object[] keyValues)
{
Check.NotNull(keyValues, nameof(keyValues));
@@ -66,27 +61,16 @@ public override TEntity Find(params object[] keyValues)
}
///
- /// Finds an entity with the given primary key values. If an entity with the given primary key values
- /// is being tracked by the context, then it is returned immediately without making a request to the
- /// database. Otherwise, a query is made to the dataabse for an entity with the given primary key values
- /// and this entity, if found, is attached to the context and returned. If no entity is found, then
- /// null is returned.
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
///
- /// The values of the primary key for the entity to be found.
- /// The entity found, or null.
public override Task FindAsync(params object[] keyValues)
=> FindAsync(keyValues, default(CancellationToken));
///
- /// Finds an entity with the given primary key values. If an entity with the given primary key values
- /// is being tracked by the context, then it is returned immediately without making a request to the
- /// database. Otherwise, a query is made to the dataabse for an entity with the given primary key values
- /// and this entity, if found, is attached to the context and returned. If no entity is found, then
- /// null is returned.
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
///
- /// The values of the primary key for the entity to be found.
- /// A to observe while waiting for the task to complete.
- /// The entity found, or null.
public override Task FindAsync(object[] keyValues, CancellationToken cancellationToken)
{
Check.NotNull(keyValues, nameof(keyValues));
@@ -165,28 +149,38 @@ private static Expression> BuildPredicate(IReadOnlyList
public override EntityEntry Add(TEntity entity)
- => _context.Add(Check.NotNull(entity, nameof(entity)));
+ => _context.Add(entity);
+
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public override Task> AddAsync(
+ TEntity entity,
+ CancellationToken cancellationToken = default(CancellationToken))
+ => _context.AddAsync(entity, cancellationToken);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public override EntityEntry Attach(TEntity entity)
- => _context.Attach(Check.NotNull(entity, nameof(entity)));
+ => _context.Attach(entity);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public override EntityEntry Remove(TEntity entity)
- => _context.Remove(Check.NotNull(entity, nameof(entity)));
+ => _context.Remove(entity);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public override EntityEntry Update(TEntity entity)
- => _context.Update(Check.NotNull(entity, nameof(entity)));
+ => _context.Update(entity);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
@@ -194,7 +188,15 @@ public override EntityEntry Update(TEntity entity)
///
public override void AddRange(params TEntity[] entities)
// ReSharper disable once CoVariantArrayConversion
- => _context.AddRange(Check.NotNull(entities, nameof(entities)));
+ => _context.AddRange(entities);
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public override Task AddRangeAsync(params TEntity[] entities)
+ // ReSharper disable once CoVariantArrayConversion
+ => _context.AddRangeAsync(entities);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
@@ -202,7 +204,7 @@ public override void AddRange(params TEntity[] entities)
///
public override void AttachRange(params TEntity[] entities)
// ReSharper disable once CoVariantArrayConversion
- => _context.AttachRange(Check.NotNull(entities, nameof(entities)));
+ => _context.AttachRange(entities);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
@@ -210,7 +212,7 @@ public override void AttachRange(params TEntity[] entities)
///
public override void RemoveRange(params TEntity[] entities)
// ReSharper disable once CoVariantArrayConversion
- => _context.RemoveRange(Check.NotNull(entities, nameof(entities)));
+ => _context.RemoveRange(entities);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
@@ -218,35 +220,44 @@ public override void RemoveRange(params TEntity[] entities)
///
public override void UpdateRange(params TEntity[] entities)
// ReSharper disable once CoVariantArrayConversion
- => _context.UpdateRange(Check.NotNull(entities, nameof(entities)));
+ => _context.UpdateRange(entities);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public override void AddRange(IEnumerable entities)
- => _context.AddRange(Check.NotNull(entities, nameof(entities)));
+ => _context.AddRange(entities);
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ public override Task AddRangeAsync(
+ IEnumerable entities,
+ CancellationToken cancellationToken = default(CancellationToken))
+ => _context.AddRangeAsync(entities, cancellationToken);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public override void AttachRange(IEnumerable entities)
- => _context.AttachRange(Check.NotNull(entities, nameof(entities)));
+ => _context.AttachRange(entities);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public override void RemoveRange(IEnumerable entities)
- => _context.RemoveRange(Check.NotNull(entities, nameof(entities)));
+ => _context.RemoveRange(entities);
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
public override void UpdateRange(IEnumerable entities)
- => _context.UpdateRange(Check.NotNull(entities, nameof(entities)));
+ => _context.UpdateRange(entities);
IEnumerator IEnumerable.GetEnumerator() => _entityQueryable.Value.GetEnumerator();
diff --git a/src/Microsoft.EntityFrameworkCore/Microsoft.EntityFrameworkCore.csproj b/src/Microsoft.EntityFrameworkCore/Microsoft.EntityFrameworkCore.csproj
index 729b9994c5a..3ac7b510177 100644
--- a/src/Microsoft.EntityFrameworkCore/Microsoft.EntityFrameworkCore.csproj
+++ b/src/Microsoft.EntityFrameworkCore/Microsoft.EntityFrameworkCore.csproj
@@ -186,6 +186,7 @@
+
diff --git a/src/Microsoft.EntityFrameworkCore/ValueGeneration/HiLoValueGenerator.cs b/src/Microsoft.EntityFrameworkCore/ValueGeneration/HiLoValueGenerator.cs
index 7d669b1a8c9..d798b7f33ed 100644
--- a/src/Microsoft.EntityFrameworkCore/ValueGeneration/HiLoValueGenerator.cs
+++ b/src/Microsoft.EntityFrameworkCore/ValueGeneration/HiLoValueGenerator.cs
@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Utilities;
@@ -42,10 +44,26 @@ protected HiLoValueGenerator([NotNull] HiLoValueGeneratorState generatorState)
/// The value to be assigned to a property.
public override TValue Next(EntityEntry entry) => _generatorState.Next(GetNewLowValue);
+ ///
+ /// Gets a value to be assigned to a property.
+ ///
+ /// The change tracking entry of the entity for which the value is being generated.
+ /// The value to be assigned to a property.
+ public override Task NextAsync(
+ EntityEntry entry, CancellationToken cancellationToken = default(CancellationToken))
+ => _generatorState.NextAsync(GetNewLowValueAsync);
+
///
/// Gets the low value for the next block of values to be used.
///
/// The low value for the next block of values to be used.
protected abstract long GetNewLowValue();
+
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ protected virtual Task GetNewLowValueAsync(CancellationToken cancellationToken = default(CancellationToken))
+ => Task.FromResult(GetNewLowValue());
}
}
diff --git a/src/Microsoft.EntityFrameworkCore/ValueGeneration/HiLoValueGeneratorState.cs b/src/Microsoft.EntityFrameworkCore/ValueGeneration/HiLoValueGeneratorState.cs
index 3b02b694161..32ee82603b0 100644
--- a/src/Microsoft.EntityFrameworkCore/ValueGeneration/HiLoValueGeneratorState.cs
+++ b/src/Microsoft.EntityFrameworkCore/ValueGeneration/HiLoValueGeneratorState.cs
@@ -4,6 +4,7 @@
using System;
using System.Globalization;
using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
@@ -15,7 +16,7 @@ namespace Microsoft.EntityFrameworkCore.ValueGeneration
///
public class HiLoValueGeneratorState
{
- private readonly object _lock;
+ private readonly AsyncLock _asyncLock = new AsyncLock();
private HiLoValue _currentValue;
private readonly int _blockSize;
@@ -35,7 +36,6 @@ public HiLoValueGeneratorState(int blockSize)
_blockSize = blockSize;
_currentValue = new HiLoValue(-1, 0);
- _lock = new object();
}
///
@@ -57,7 +57,7 @@ public virtual TValue Next([NotNull] Func getNewLowValue)
// gets a chance to use the new new value, so use a while here to do it all again.
while (newValue.Low >= newValue.High)
{
- lock (_lock)
+ using (_asyncLock.Lock())
{
// Once inside the lock check to see if another thread already got a new block, in which
// case just get a value out of the new block instead of requesting one.
@@ -74,9 +74,54 @@ public virtual TValue Next([NotNull] Func getNewLowValue)
}
}
- return (TValue)Convert.ChangeType(newValue.Low, typeof(TValue), CultureInfo.InvariantCulture);
+ return ConvertResult(newValue);
}
+ ///
+ /// Gets a value to be assigned to a property.
+ ///
+ /// The type of values being generated.
+ ///
+ /// A function to get the next low value if needed.
+ ///
+ /// A to observe while waiting for the task to complete.
+ /// The value to be assigned to a property.
+ public virtual async Task NextAsync(
+ [NotNull] Func> getNewLowValue,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ Check.NotNull(getNewLowValue, nameof(getNewLowValue));
+
+ var newValue = GetNextValue();
+
+ // If the chosen value is outside of the current block then we need a new block.
+ // It is possible that other threads will use all of the new block before this thread
+ // gets a chance to use the new new value, so use a while here to do it all again.
+ while (newValue.Low >= newValue.High)
+ {
+ using (await _asyncLock.LockAsync())
+ {
+ // Once inside the lock check to see if another thread already got a new block, in which
+ // case just get a value out of the new block instead of requesting one.
+ if (newValue.High == _currentValue.High)
+ {
+ var newCurrent = await getNewLowValue(cancellationToken);
+ newValue = new HiLoValue(newCurrent, newCurrent + _blockSize);
+ _currentValue = newValue;
+ }
+ else
+ {
+ newValue = GetNextValue();
+ }
+ }
+ }
+
+ return ConvertResult(newValue);
+ }
+
+ private static TValue ConvertResult(HiLoValue newValue)
+ => (TValue)Convert.ChangeType(newValue.Low, typeof(TValue), CultureInfo.InvariantCulture);
+
private HiLoValue GetNextValue()
{
HiLoValue originalValue;
diff --git a/src/Microsoft.EntityFrameworkCore/ValueGeneration/ValueGenerator.cs b/src/Microsoft.EntityFrameworkCore/ValueGeneration/ValueGenerator.cs
index 869e8562b15..352f5047062 100644
--- a/src/Microsoft.EntityFrameworkCore/ValueGeneration/ValueGenerator.cs
+++ b/src/Microsoft.EntityFrameworkCore/ValueGeneration/ValueGenerator.cs
@@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
@@ -17,7 +19,8 @@ public abstract class ValueGenerator
///
/// The change tracking entry of the entity for which the value is being generated.
/// The value to be assigned to a property.
- public virtual object Next([NotNull] EntityEntry entry) => NextValue(entry);
+ public virtual object Next([NotNull] EntityEntry entry)
+ => NextValue(entry);
///
/// Template method to be overridden by implementations to perform value generation.
@@ -26,6 +29,26 @@ public abstract class ValueGenerator
/// The generated value.
protected abstract object NextValue([NotNull] EntityEntry entry);
+ ///
+ /// Gets a value to be assigned to a property.
+ ///
+ /// The change tracking entry of the entity for which the value is being generated.
+ /// The value to be assigned to a property.
+ public virtual Task NextAsync(
+ [NotNull] EntityEntry entry,
+ CancellationToken cancellationToken = default(CancellationToken))
+ => NextValueAsync(entry, cancellationToken);
+
+ ///
+ /// Template method to be overridden by implementations to perform value generation.
+ ///
+ /// The change tracking entry of the entity for which the value is being generated.
+ /// The generated value.
+ protected virtual Task NextValueAsync(
+ [NotNull] EntityEntry entry,
+ CancellationToken cancellationToken = default(CancellationToken))
+ => Task.FromResult(NextValue(entry));
+
///
///
/// Gets a value indicating whether the values generated are temporary (i.e they should be replaced
diff --git a/src/Microsoft.EntityFrameworkCore/ValueGeneration/ValueGenerator`.cs b/src/Microsoft.EntityFrameworkCore/ValueGeneration/ValueGenerator`.cs
index 859db91a7da..c54c275e977 100644
--- a/src/Microsoft.EntityFrameworkCore/ValueGeneration/ValueGenerator`.cs
+++ b/src/Microsoft.EntityFrameworkCore/ValueGeneration/ValueGenerator`.cs
@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
@@ -18,11 +20,32 @@ public abstract class ValueGenerator : ValueGenerator
/// The generated value.
public new abstract TValue Next([NotNull] EntityEntry entry);
+ ///
+ /// Template method to be overridden by implementations to perform value generation.
+ ///
+ /// The change tracking entry of the entity for which the value is being generated.
+ /// The generated value.
+ public new virtual Task NextAsync(
+ [NotNull] EntityEntry entry,
+ CancellationToken cancellationToken = default(CancellationToken))
+ => Task.FromResult(Next(entry));
+
+ ///
+ /// Gets a value to be assigned to a property.
+ ///
+ /// The change tracking entry of the entity for which the value is being generated.
+ /// The value to be assigned to a property.
+ protected override object NextValue(EntityEntry entry)
+ => Next(entry);
+
///
/// Gets a value to be assigned to a property.
///
/// The change tracking entry of the entity for which the value is being generated.
/// The value to be assigned to a property.
- protected override object NextValue(EntityEntry entry) => Next(entry);
+ protected override Task NextValueAsync(
+ EntityEntry entry,
+ CancellationToken cancellationToken = default(CancellationToken))
+ => Task.FromResult((object)Next(entry));
}
}
diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/SequenceEndToEndTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/SequenceEndToEndTest.cs
index f0c5b4a837f..aaf1369e83a 100644
--- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/SequenceEndToEndTest.cs
+++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/SequenceEndToEndTest.cs
@@ -73,8 +73,8 @@ public async Task Can_use_sequence_end_to_end_async()
using (var context = new BronieContext(serviceProvider, "BroniesAsync"))
{
- context.Database.EnsureDeleted();
- context.Database.EnsureCreated();
+ await context.Database.EnsureDeletedAsync();
+ await context.Database.EnsureCreatedAsync();
}
await AddEntitiesAsync(serviceProvider, "BroniesAsync");
@@ -106,8 +106,8 @@ private static async Task AddEntitiesAsync(IServiceProvider serviceProvider, str
{
for (var i = 0; i < 10; i++)
{
- context.Add(new Pegasus { Name = "Rainbow Dash " + i });
- context.Add(new Pegasus { Name = "Fluttershy " + i });
+ await context.AddAsync(new Pegasus { Name = "Rainbow Dash " + i });
+ await context.AddAsync(new Pegasus { Name = "Fluttershy " + i });
}
await context.SaveChangesAsync();
@@ -115,7 +115,6 @@ private static async Task AddEntitiesAsync(IServiceProvider serviceProvider, str
}
[ConditionalFact]
- [PlatformSkipCondition(TestPlatform.Mac | TestPlatform.Linux, SkipReason = "Test is flaky on OSX/Linux. See https://github.com/dotnet/corefx/issues/8701")]
public async Task Can_use_sequence_end_to_end_from_multiple_contexts_concurrently_async()
{
var serviceProvider = new ServiceCollection()
@@ -124,8 +123,8 @@ public async Task Can_use_sequence_end_to_end_from_multiple_contexts_concurrentl
using (var context = new BronieContext(serviceProvider, "ManyBronies"))
{
- context.Database.EnsureDeleted();
- context.Database.EnsureCreated();
+ await context.Database.EnsureDeletedAsync();
+ await context.Database.EnsureCreatedAsync();
}
const int threadCount = 50;
diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerSequenceValueGeneratorTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerSequenceValueGeneratorTest.cs
index f63c7cbcff6..85b664184f2 100644
--- a/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerSequenceValueGeneratorTest.cs
+++ b/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerSequenceValueGeneratorTest.cs
@@ -20,31 +20,47 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Tests
{
public class SqlServerSequenceValueGeneratorTest
{
- [Fact]
- public void Generates_sequential_int_values() => Generates_sequential_values();
-
- [Fact]
- public void Generates_sequential_long_values() => Generates_sequential_values();
-
- [Fact]
- public void Generates_sequential_short_values() => Generates_sequential_values();
-
- [Fact]
- public void Generates_sequential_byte_values() => Generates_sequential_values();
-
- [Fact]
- public void Generates_sequential_uint_values() => Generates_sequential_values();
-
- [Fact]
- public void Generates_sequential_ulong_values() => Generates_sequential_values();
-
- [Fact]
- public void Generates_sequential_ushort_values() => Generates_sequential_values();
-
- [Fact]
- public void Generates_sequential_sbyte_values() => Generates_sequential_values();
-
- public void Generates_sequential_values()
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task Generates_sequential_int_values(bool async) => await Generates_sequential_values(async);
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task Generates_sequential_long_values(bool async) => await Generates_sequential_values(async);
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task Generates_sequential_short_values(bool async) => await Generates_sequential_values(async);
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task Generates_sequential_byte_values(bool async) => await Generates_sequential_values(async);
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task Generates_sequential_uint_values(bool async) => await Generates_sequential_values(async);
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task Generates_sequential_ulong_values(bool async) => await Generates_sequential_values(async);
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task Generates_sequential_ushort_values(bool async) => await Generates_sequential_values(async);
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task Generates_sequential_sbyte_values(bool async) => await Generates_sequential_values(async);
+
+ public async Task Generates_sequential_values(bool async)
{
const int blockSize = 4;
@@ -60,17 +76,21 @@ public void Generates_sequential_values()
for (var i = 1; i <= 27; i++)
{
- Assert.Equal(i, (int)Convert.ChangeType(generator.Next(null), typeof(int), CultureInfo.InvariantCulture));
+ var value = async
+ ? await generator.NextAsync(null)
+ : generator.Next(null);
+
+ Assert.Equal(i, (int)Convert.ChangeType(value, typeof(int), CultureInfo.InvariantCulture));
}
}
[Fact]
- public void Multiple_threads_can_use_the_same_generator_state()
+ public async Task Multiple_threads_can_use_the_same_generator_state()
{
const int threadCount = 50;
const int valueCount = 35;
- var generatedValues = GenerateValuesInMultipleThreads(threadCount, valueCount);
+ var generatedValues = await GenerateValuesInMultipleThreads(threadCount, valueCount);
// Check that each value was generated once and only once
var checks = new bool[threadCount * valueCount];
@@ -86,7 +106,7 @@ public void Multiple_threads_can_use_the_same_generator_state()
Assert.True(checks.All(c => c));
}
- private IEnumerable> GenerateValuesInMultipleThreads(int threadCount, int valueCount)
+ private async Task>> GenerateValuesInMultipleThreads(int threadCount, int valueCount)
{
const int blockSize = 10;
@@ -99,25 +119,34 @@ private IEnumerable> GenerateValuesInMultipleThreads(int threadCount,
var executor = new FakeRawSqlCommandBuilder(blockSize);
var sqlGenerator = new SqlServerUpdateSqlGenerator(new SqlServerSqlGenerationHelper(), new SqlServerTypeMapper());
- var tests = new Action[threadCount];
+ var tests = new Func[threadCount];
var generatedValues = new List[threadCount];
for (var i = 0; i < tests.Length; i++)
{
var testNumber = i;
generatedValues[testNumber] = new List();
- tests[testNumber] = () =>
+ tests[testNumber] = async () =>
{
for (var j = 0; j < valueCount; j++)
{
var connection = CreateConnection(serviceProvider);
var generator = new SqlServerSequenceHiLoValueGenerator(executor, sqlGenerator, state, connection);
- generatedValues[testNumber].Add(generator.Next(null));
+ var value = j % 2 == 0
+ ? await generator.NextAsync(null)
+ : generator.Next(null);
+
+ generatedValues[testNumber].Add(value);
}
};
}
- Parallel.Invoke(tests);
+ var tasks = tests.Select(Task.Run).ToArray();
+
+ foreach (var t in tasks)
+ {
+ await t;
+ }
return generatedValues;
}
diff --git a/test/Microsoft.EntityFrameworkCore.Tests/DbContextTest.cs b/test/Microsoft.EntityFrameworkCore.Tests/DbContextTest.cs
index af74b717bbc..627d34b81c5 100644
--- a/test/Microsoft.EntityFrameworkCore.Tests/DbContextTest.cs
+++ b/test/Microsoft.EntityFrameworkCore.Tests/DbContextTest.cs
@@ -383,32 +383,46 @@ public virtual void Resume()
}
[Fact]
- public void Can_add_existing_entities_to_context_to_be_deleted()
+ public async Task Can_add_existing_entities_to_context_to_be_deleted()
{
- TrackEntitiesTest((c, e) => c.Remove(e), (c, e) => c.Remove(e), EntityState.Deleted);
+ await TrackEntitiesTest((c, e) => c.Remove(e), (c, e) => c.Remove(e), EntityState.Deleted);
}
[Fact]
- public void Can_add_new_entities_to_context_with_graph_method()
+ public async Task Can_add_new_entities_to_context_with_graph_method()
{
- TrackEntitiesTest((c, e) => c.Add(e), (c, e) => c.Add(e), EntityState.Added);
+ await TrackEntitiesTest((c, e) => c.Add(e), (c, e) => c.Add(e), EntityState.Added);
}
[Fact]
- public void Can_add_existing_entities_to_context_to_be_attached_with_graph_method()
+ public async Task Can_add_new_entities_to_context_with_graph_method_async()
{
- TrackEntitiesTest((c, e) => c.Attach(e), (c, e) => c.Attach(e), EntityState.Unchanged);
+ await TrackEntitiesTest((c, e) => c.AddAsync(e), (c, e) => c.AddAsync(e), EntityState.Added);
}
[Fact]
- public void Can_add_existing_entities_to_context_to_be_updated_with_graph_method()
+ public async Task Can_add_existing_entities_to_context_to_be_attached_with_graph_method()
{
- TrackEntitiesTest((c, e) => c.Update(e), (c, e) => c.Update(e), EntityState.Modified);
+ await TrackEntitiesTest((c, e) => c.Attach(e), (c, e) => c.Attach(e), EntityState.Unchanged);
}
- private static void TrackEntitiesTest(
+ [Fact]
+ public async Task Can_add_existing_entities_to_context_to_be_updated_with_graph_method()
+ {
+ await TrackEntitiesTest((c, e) => c.Update(e), (c, e) => c.Update(e), EntityState.Modified);
+ }
+
+ private static Task TrackEntitiesTest(
Func> categoryAdder,
Func> productAdder, EntityState expectedState)
+ => TrackEntitiesTest(
+ (c, e) => Task.FromResult(categoryAdder(c, e)),
+ (c, e) => Task.FromResult(productAdder(c, e)),
+ expectedState);
+
+ private static async Task TrackEntitiesTest(
+ Func>> categoryAdder,
+ Func>> productAdder, EntityState expectedState)
{
using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
{
@@ -417,10 +431,10 @@ private static void TrackEntitiesTest(
var product1 = new Product { Id = 1, Name = "Marmite", Price = 7.99m };
var product2 = new Product { Id = 2, Name = "Bovril", Price = 4.99m };
- var categoryEntry1 = categoryAdder(context, category1);
- var categoryEntry2 = categoryAdder(context, category2);
- var productEntry1 = productAdder(context, product1);
- var productEntry2 = productAdder(context, product2);
+ var categoryEntry1 = await categoryAdder(context, category1);
+ var categoryEntry2 = await categoryAdder(context, category2);
+ var productEntry1 = await productAdder(context, product1);
+ var productEntry2 = await productAdder(context, product2);
Assert.Same(category1, categoryEntry1.Entity);
Assert.Same(category2, categoryEntry2.Entity);
@@ -445,32 +459,54 @@ private static void TrackEntitiesTest(
}
[Fact]
- public void Can_add_multiple_new_entities_to_context()
+ public async Task Can_add_multiple_new_entities_to_context()
+ {
+ await TrackMultipleEntitiesTest((c, e) => c.AddRange(e[0], e[1]), (c, e) => c.AddRange(e[0], e[1]), EntityState.Added);
+ }
+
+ [Fact]
+ public async Task Can_add_multiple_new_entities_to_context_async()
{
- TrackMultipleEntitiesTest((c, e) => c.AddRange(e[0], e[1]), (c, e) => c.AddRange(e[0], e[1]), EntityState.Added);
+ await TrackMultipleEntitiesTest((c, e) => c.AddRangeAsync(e[0], e[1]), (c, e) => c.AddRangeAsync(e[0], e[1]), EntityState.Added);
}
[Fact]
- public void Can_add_multiple_existing_entities_to_context_to_be_attached()
+ public async Task Can_add_multiple_existing_entities_to_context_to_be_attached()
{
- TrackMultipleEntitiesTest((c, e) => c.AttachRange(e[0], e[1]), (c, e) => c.AttachRange(e[0], e[1]), EntityState.Unchanged);
+ await TrackMultipleEntitiesTest((c, e) => c.AttachRange(e[0], e[1]), (c, e) => c.AttachRange(e[0], e[1]), EntityState.Unchanged);
}
[Fact]
- public void Can_add_multiple_existing_entities_to_context_to_be_updated()
+ public async Task Can_add_multiple_existing_entities_to_context_to_be_updated()
{
- TrackMultipleEntitiesTest((c, e) => c.UpdateRange(e[0], e[1]), (c, e) => c.UpdateRange(e[0], e[1]), EntityState.Modified);
+ await TrackMultipleEntitiesTest((c, e) => c.UpdateRange(e[0], e[1]), (c, e) => c.UpdateRange(e[0], e[1]), EntityState.Modified);
}
[Fact]
- public void Can_add_multiple_existing_entities_to_context_to_be_deleted()
+ public async Task Can_add_multiple_existing_entities_to_context_to_be_deleted()
{
- TrackMultipleEntitiesTest((c, e) => c.RemoveRange(e[0], e[1]), (c, e) => c.RemoveRange(e[0], e[1]), EntityState.Deleted);
+ await TrackMultipleEntitiesTest((c, e) => c.RemoveRange(e[0], e[1]), (c, e) => c.RemoveRange(e[0], e[1]), EntityState.Deleted);
}
- private static void TrackMultipleEntitiesTest(
+ private static Task TrackMultipleEntitiesTest(
Action categoryAdder,
Action productAdder, EntityState expectedState)
+ => TrackMultipleEntitiesTest(
+ (c, e) =>
+ {
+ categoryAdder(c, e);
+ return Task.FromResult(0);
+ },
+ (c, e) =>
+ {
+ productAdder(c, e);
+ return Task.FromResult(0);
+ },
+ expectedState);
+
+ private static async Task TrackMultipleEntitiesTest(
+ Func categoryAdder,
+ Func productAdder, EntityState expectedState)
{
using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
{
@@ -479,8 +515,8 @@ private static void TrackMultipleEntitiesTest(
var product1 = new Product { Id = 1, Name = "Marmite", Price = 7.99m };
var product2 = new Product { Id = 2, Name = "Bovril", Price = 4.99m };
- categoryAdder(context, new[] { category1, category2 });
- productAdder(context, new[] { product1, product2 });
+ await categoryAdder(context, new[] { category1, category2 });
+ await productAdder(context, new[] { product1, product2 });
Assert.Same(category1, context.Entry(category1).Entity);
Assert.Same(category2, context.Entry(category2).Entity);
@@ -500,41 +536,55 @@ private static void TrackMultipleEntitiesTest(
}
[Fact]
- public void Can_add_existing_entities_with_default_value_to_context_to_be_deleted()
+ public async Task Can_add_existing_entities_with_default_value_to_context_to_be_deleted()
{
- TrackEntitiesDefaultValueTest((c, e) => c.Remove(e), (c, e) => c.Remove(e), EntityState.Deleted);
+ await TrackEntitiesDefaultValueTest((c, e) => c.Remove(e), (c, e) => c.Remove(e), EntityState.Deleted);
}
[Fact]
- public void Can_add_new_entities_with_default_value_to_context_with_graph_method()
+ public async Task Can_add_new_entities_with_default_value_to_context_with_graph_method()
{
- TrackEntitiesDefaultValueTest((c, e) => c.Add(e), (c, e) => c.Add(e), EntityState.Added);
+ await TrackEntitiesDefaultValueTest((c, e) => c.Add(e), (c, e) => c.Add(e), EntityState.Added);
}
[Fact]
- public void Can_add_existing_entities_with_default_value_to_context_to_be_attached_with_graph_method()
+ public async Task Can_add_new_entities_with_default_value_to_context_with_graph_method_async()
{
- TrackEntitiesDefaultValueTest((c, e) => c.Attach(e), (c, e) => c.Attach(e), EntityState.Unchanged);
+ await TrackEntitiesDefaultValueTest((c, e) => c.AddAsync(e), (c, e) => c.AddAsync(e), EntityState.Added);
}
[Fact]
- public void Can_add_existing_entities_with_default_value_to_context_to_be_updated_with_graph_method()
+ public async Task Can_add_existing_entities_with_default_value_to_context_to_be_attached_with_graph_method()
{
- TrackEntitiesDefaultValueTest((c, e) => c.Update(e), (c, e) => c.Update(e), EntityState.Modified);
+ await TrackEntitiesDefaultValueTest((c, e) => c.Attach(e), (c, e) => c.Attach(e), EntityState.Unchanged);
}
- // Issue #3890
- private static void TrackEntitiesDefaultValueTest(
+ [Fact]
+ public async Task Can_add_existing_entities_with_default_value_to_context_to_be_updated_with_graph_method()
+ {
+ await TrackEntitiesDefaultValueTest((c, e) => c.Update(e), (c, e) => c.Update(e), EntityState.Modified);
+ }
+
+ private static Task TrackEntitiesDefaultValueTest(
Func> categoryAdder,
Func> productAdder, EntityState expectedState)
+ => TrackEntitiesDefaultValueTest(
+ (c, e) => Task.FromResult(categoryAdder(c, e)),
+ (c, e) => Task.FromResult(productAdder(c, e)),
+ expectedState);
+
+ // Issue #3890
+ private static async Task TrackEntitiesDefaultValueTest(
+ Func>> categoryAdder,
+ Func>> productAdder, EntityState expectedState)
{
using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
{
var category1 = new Category { Id = 0, Name = "Beverages" };
var product1 = new Product { Id = 0, Name = "Marmite", Price = 7.99m };
- var categoryEntry1 = categoryAdder(context, category1);
- var productEntry1 = productAdder(context, product1);
+ var categoryEntry1 = await categoryAdder(context, category1);
+ var productEntry1 = await productAdder(context, product1);
Assert.Same(category1, categoryEntry1.Entity);
Assert.Same(product1, productEntry1.Entity);
@@ -551,41 +601,63 @@ private static void TrackEntitiesDefaultValueTest(
}
[Fact]
- public void Can_add_multiple_new_entities_with_default_values_to_context()
+ public async Task Can_add_multiple_new_entities_with_default_values_to_context()
{
- TrackMultipleEntitiesDefaultValuesTest((c, e) => c.AddRange(e[0]), (c, e) => c.AddRange(e[0]), EntityState.Added);
+ await TrackMultipleEntitiesDefaultValuesTest((c, e) => c.AddRange(e[0]), (c, e) => c.AddRange(e[0]), EntityState.Added);
}
[Fact]
- public void Can_add_multiple_existing_entities_with_default_values_to_context_to_be_attached()
+ public async Task Can_add_multiple_new_entities_with_default_values_to_context_async()
{
- TrackMultipleEntitiesDefaultValuesTest((c, e) => c.AttachRange(e[0]), (c, e) => c.AttachRange(e[0]), EntityState.Unchanged);
+ await TrackMultipleEntitiesDefaultValuesTest((c, e) => c.AddRangeAsync(e[0]), (c, e) => c.AddRangeAsync(e[0]), EntityState.Added);
}
[Fact]
- public void Can_add_multiple_existing_entities_with_default_values_to_context_to_be_updated()
+ public async Task Can_add_multiple_existing_entities_with_default_values_to_context_to_be_attached()
{
- TrackMultipleEntitiesDefaultValuesTest((c, e) => c.UpdateRange(e[0]), (c, e) => c.UpdateRange(e[0]), EntityState.Modified);
+ await TrackMultipleEntitiesDefaultValuesTest((c, e) => c.AttachRange(e[0]), (c, e) => c.AttachRange(e[0]), EntityState.Unchanged);
}
[Fact]
- public void Can_add_multiple_existing_entities_with_default_values_to_context_to_be_deleted()
+ public async Task Can_add_multiple_existing_entities_with_default_values_to_context_to_be_updated()
{
- TrackMultipleEntitiesDefaultValuesTest((c, e) => c.RemoveRange(e[0]), (c, e) => c.RemoveRange(e[0]), EntityState.Deleted);
+ await TrackMultipleEntitiesDefaultValuesTest((c, e) => c.UpdateRange(e[0]), (c, e) => c.UpdateRange(e[0]), EntityState.Modified);
}
- // Issue #3890
- private static void TrackMultipleEntitiesDefaultValuesTest(
+ [Fact]
+ public async Task Can_add_multiple_existing_entities_with_default_values_to_context_to_be_deleted()
+ {
+ await TrackMultipleEntitiesDefaultValuesTest((c, e) => c.RemoveRange(e[0]), (c, e) => c.RemoveRange(e[0]), EntityState.Deleted);
+ }
+
+ private static Task TrackMultipleEntitiesDefaultValuesTest(
Action categoryAdder,
Action productAdder, EntityState expectedState)
+ => TrackMultipleEntitiesDefaultValuesTest(
+ (c, e) =>
+ {
+ categoryAdder(c, e);
+ return Task.FromResult(0);
+ },
+ (c, e) =>
+ {
+ productAdder(c, e);
+ return Task.FromResult(0);
+ },
+ expectedState);
+
+ // Issue #3890
+ private static async Task TrackMultipleEntitiesDefaultValuesTest(
+ Func categoryAdder,
+ Func productAdder, EntityState expectedState)
{
using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
{
var category1 = new Category { Id = 0, Name = "Beverages" };
var product1 = new Product { Id = 0, Name = "Marmite", Price = 7.99m };
- categoryAdder(context, new[] { category1 });
- productAdder(context, new[] { product1 });
+ await categoryAdder(context, new[] { category1 });
+ await productAdder(context, new[] { product1 });
Assert.Same(category1, context.Entry(category1).Entity);
Assert.Same(product1, context.Entry(product1).Entity);
@@ -604,6 +676,17 @@ public void Can_add_no_new_entities_to_context()
TrackNoEntitiesTest(c => c.AddRange(), c => c.AddRange());
}
+ [Fact]
+ public async Task Can_add_no_new_entities_to_context_async()
+ {
+ using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
+ {
+ await context.AddRangeAsync();
+ await context.AddRangeAsync();
+ Assert.Empty(context.ChangeTracker.Entries());
+ }
+ }
+
[Fact]
public void Can_add_no_existing_entities_to_context_to_be_attached()
{
@@ -633,32 +716,46 @@ private static void TrackNoEntitiesTest(Action categoryAdder, Action<
}
[Fact]
- public void Can_add_existing_entities_to_context_to_be_deleted_non_generic()
+ public async Task Can_add_existing_entities_to_context_to_be_deleted_non_generic()
+ {
+ await TrackEntitiesTestNonGeneric((c, e) => c.Remove(e), (c, e) => c.Remove(e), EntityState.Deleted);
+ }
+
+ [Fact]
+ public async Task Can_add_new_entities_to_context_non_generic_graph()
{
- TrackEntitiesTestNonGeneric((c, e) => c.Remove(e), (c, e) => c.Remove(e), EntityState.Deleted);
+ await TrackEntitiesTestNonGeneric((c, e) => c.AddAsync(e), (c, e) => c.AddAsync(e), EntityState.Added);
}
[Fact]
- public void Can_add_new_entities_to_context_non_generic_graph()
+ public async Task Can_add_new_entities_to_context_non_generic_graph_async()
{
- TrackEntitiesTestNonGeneric((c, e) => c.Add(e), (c, e) => c.Add(e), EntityState.Added);
+ await TrackEntitiesTestNonGeneric((c, e) => c.Add(e), (c, e) => c.Add(e), EntityState.Added);
}
[Fact]
- public void Can_add_existing_entities_to_context_to_be_attached_non_generic_graph()
+ public async Task Can_add_existing_entities_to_context_to_be_attached_non_generic_graph()
{
- TrackEntitiesTestNonGeneric((c, e) => c.Attach(e), (c, e) => c.Attach(e), EntityState.Unchanged);
+ await TrackEntitiesTestNonGeneric((c, e) => c.Attach(e), (c, e) => c.Attach(e), EntityState.Unchanged);
}
[Fact]
- public void Can_add_existing_entities_to_context_to_be_updated_non_generic_graph()
+ public async Task Can_add_existing_entities_to_context_to_be_updated_non_generic_graph()
{
- TrackEntitiesTestNonGeneric((c, e) => c.Update(e), (c, e) => c.Update(e), EntityState.Modified);
+ await TrackEntitiesTestNonGeneric((c, e) => c.Update(e), (c, e) => c.Update(e), EntityState.Modified);
}
- private static void TrackEntitiesTestNonGeneric(
+ private static Task TrackEntitiesTestNonGeneric(
Func categoryAdder,
Func productAdder, EntityState expectedState)
+ => TrackEntitiesTestNonGeneric(
+ (c, e) => Task.FromResult(categoryAdder(c, e)),
+ (c, e) => Task.FromResult(productAdder(c, e)),
+ expectedState);
+
+ private static async Task TrackEntitiesTestNonGeneric(
+ Func> categoryAdder,
+ Func> productAdder, EntityState expectedState)
{
using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
{
@@ -667,10 +764,10 @@ private static void TrackEntitiesTestNonGeneric(
var product1 = new Product { Id = 1, Name = "Marmite", Price = 7.99m };
var product2 = new Product { Id = 2, Name = "Bovril", Price = 4.99m };
- var categoryEntry1 = categoryAdder(context, category1);
- var categoryEntry2 = categoryAdder(context, category2);
- var productEntry1 = productAdder(context, product1);
- var productEntry2 = productAdder(context, product2);
+ var categoryEntry1 = await categoryAdder(context, category1);
+ var categoryEntry2 = await categoryAdder(context, category2);
+ var productEntry1 = await productAdder(context, product1);
+ var productEntry2 = await productAdder(context, product2);
Assert.Same(category1, categoryEntry1.Entity);
Assert.Same(category2, categoryEntry2.Entity);
@@ -695,32 +792,54 @@ private static void TrackEntitiesTestNonGeneric(
}
[Fact]
- public void Can_add_multiple_existing_entities_to_context_to_be_deleted_Enumerable()
+ public async Task Can_add_multiple_existing_entities_to_context_to_be_deleted_Enumerable()
{
- TrackMultipleEntitiesTestEnumerable((c, e) => c.RemoveRange(e), (c, e) => c.RemoveRange(e), EntityState.Deleted);
+ await TrackMultipleEntitiesTestEnumerable((c, e) => c.RemoveRange(e), (c, e) => c.RemoveRange(e), EntityState.Deleted);
}
[Fact]
- public void Can_add_multiple_new_entities_to_context_Enumerable_graph()
+ public async Task Can_add_multiple_new_entities_to_context_Enumerable_graph()
{
- TrackMultipleEntitiesTestEnumerable((c, e) => c.AddRange(e), (c, e) => c.AddRange(e), EntityState.Added);
+ await TrackMultipleEntitiesTestEnumerable((c, e) => c.AddRange(e), (c, e) => c.AddRange(e), EntityState.Added);
}
[Fact]
- public void Can_add_multiple_existing_entities_to_context_to_be_attached_Enumerable_graph()
+ public async Task Can_add_multiple_new_entities_to_context_Enumerable_graph_async()
{
- TrackMultipleEntitiesTestEnumerable((c, e) => c.AttachRange(e), (c, e) => c.AttachRange(e), EntityState.Unchanged);
+ await TrackMultipleEntitiesTestEnumerable((c, e) => c.AddRangeAsync(e), (c, e) => c.AddRangeAsync(e), EntityState.Added);
}
[Fact]
- public void Can_add_multiple_existing_entities_to_context_to_be_updated_Enumerable_graph()
+ public async Task Can_add_multiple_existing_entities_to_context_to_be_attached_Enumerable_graph()
{
- TrackMultipleEntitiesTestEnumerable((c, e) => c.UpdateRange(e), (c, e) => c.UpdateRange(e), EntityState.Modified);
+ await TrackMultipleEntitiesTestEnumerable((c, e) => c.AttachRange(e), (c, e) => c.AttachRange(e), EntityState.Unchanged);
}
- private static void TrackMultipleEntitiesTestEnumerable(
+ [Fact]
+ public async Task Can_add_multiple_existing_entities_to_context_to_be_updated_Enumerable_graph()
+ {
+ await TrackMultipleEntitiesTestEnumerable((c, e) => c.UpdateRange(e), (c, e) => c.UpdateRange(e), EntityState.Modified);
+ }
+
+ private static Task TrackMultipleEntitiesTestEnumerable(
Action> categoryAdder,
Action> productAdder, EntityState expectedState)
+ => TrackMultipleEntitiesTestEnumerable(
+ (c, e) =>
+ {
+ categoryAdder(c, e);
+ return Task.FromResult(0);
+ },
+ (c, e) =>
+ {
+ productAdder(c, e);
+ return Task.FromResult(0);
+ },
+ expectedState);
+
+ private static async Task TrackMultipleEntitiesTestEnumerable(
+ Func, Task> categoryAdder,
+ Func, Task> productAdder, EntityState expectedState)
{
using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
{
@@ -729,8 +848,8 @@ private static void TrackMultipleEntitiesTestEnumerable(
var product1 = new Product { Id = 1, Name = "Marmite", Price = 7.99m };
var product2 = new Product { Id = 2, Name = "Bovril", Price = 4.99m };
- categoryAdder(context, new List { category1, category2 });
- productAdder(context, new List { product1, product2 });
+ await categoryAdder(context, new List { category1, category2 });
+ await productAdder(context, new List { product1, product2 });
Assert.Same(category1, context.Entry(category1).Entity);
Assert.Same(category2, context.Entry(category2).Entity);
@@ -750,41 +869,55 @@ private static void TrackMultipleEntitiesTestEnumerable(
}
[Fact]
- public void Can_add_existing_entities_with_default_value_to_context_to_be_deleted_non_generic()
+ public async Task Can_add_existing_entities_with_default_value_to_context_to_be_deleted_non_generic()
{
- TrackEntitiesDefaultValuesTestNonGeneric((c, e) => c.Remove(e), (c, e) => c.Remove(e), EntityState.Deleted);
+ await TrackEntitiesDefaultValuesTestNonGeneric((c, e) => c.Remove(e), (c, e) => c.Remove(e), EntityState.Deleted);
}
[Fact]
- public void Can_add_new_entities_with_default_value_to_context_non_generic_graph()
+ public async Task Can_add_new_entities_with_default_value_to_context_non_generic_graph()
{
- TrackEntitiesDefaultValuesTestNonGeneric((c, e) => c.Add(e), (c, e) => c.Add(e), EntityState.Added);
+ await TrackEntitiesDefaultValuesTestNonGeneric((c, e) => c.Add(e), (c, e) => c.Add(e), EntityState.Added);
}
[Fact]
- public void Can_add_existing_entities_with_default_value_to_context_to_be_attached_non_generic_graph()
+ public async Task Can_add_new_entities_with_default_value_to_context_non_generic_graph_async()
{
- TrackEntitiesDefaultValuesTestNonGeneric((c, e) => c.Attach(e), (c, e) => c.Attach(e), EntityState.Unchanged);
+ await TrackEntitiesDefaultValuesTestNonGeneric((c, e) => c.AddAsync(e), (c, e) => c.AddAsync(e), EntityState.Added);
}
[Fact]
- public void Can_add_existing_entities_with_default_value_to_context_to_be_updated_non_generic_graph()
+ public async Task Can_add_existing_entities_with_default_value_to_context_to_be_attached_non_generic_graph()
{
- TrackEntitiesDefaultValuesTestNonGeneric((c, e) => c.Update(e), (c, e) => c.Update(e), EntityState.Modified);
+ await TrackEntitiesDefaultValuesTestNonGeneric((c, e) => c.Attach(e), (c, e) => c.Attach(e), EntityState.Unchanged);
}
- // Issue #3890
- private static void TrackEntitiesDefaultValuesTestNonGeneric(
+ [Fact]
+ public async Task Can_add_existing_entities_with_default_value_to_context_to_be_updated_non_generic_graph()
+ {
+ await TrackEntitiesDefaultValuesTestNonGeneric((c, e) => c.Update(e), (c, e) => c.Update(e), EntityState.Modified);
+ }
+
+ private static Task TrackEntitiesDefaultValuesTestNonGeneric(
Func categoryAdder,
Func productAdder, EntityState expectedState)
+ => TrackEntitiesDefaultValuesTestNonGeneric(
+ (c, e) => Task.FromResult(categoryAdder(c, e)),
+ (c, e) => Task.FromResult(productAdder(c, e)),
+ expectedState);
+
+ // Issue #3890
+ private static async Task TrackEntitiesDefaultValuesTestNonGeneric(
+ Func> categoryAdder,
+ Func> productAdder, EntityState expectedState)
{
using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
{
var category1 = new Category { Id = 0, Name = "Beverages" };
var product1 = new Product { Id = 0, Name = "Marmite", Price = 7.99m };
- var categoryEntry1 = categoryAdder(context, category1);
- var productEntry1 = productAdder(context, product1);
+ var categoryEntry1 = await categoryAdder(context, category1);
+ var productEntry1 = await productAdder(context, product1);
Assert.Same(category1, categoryEntry1.Entity);
Assert.Same(product1, productEntry1.Entity);
@@ -801,41 +934,63 @@ private static void TrackEntitiesDefaultValuesTestNonGeneric(
}
[Fact]
- public void Can_add_multiple_existing_entities_with_default_values_to_context_to_be_deleted_Enumerable()
+ public async Task Can_add_multiple_existing_entities_with_default_values_to_context_to_be_deleted_Enumerable()
{
- TrackMultipleEntitiesDefaultValueTestEnumerable((c, e) => c.RemoveRange(e), (c, e) => c.RemoveRange(e), EntityState.Deleted);
+ await TrackMultipleEntitiesDefaultValueTestEnumerable((c, e) => c.RemoveRange(e), (c, e) => c.RemoveRange(e), EntityState.Deleted);
}
[Fact]
- public void Can_add_multiple_new_entities_with_default_values_to_context_Enumerable_graph()
+ public async Task Can_add_multiple_new_entities_with_default_values_to_context_Enumerable_graph()
{
- TrackMultipleEntitiesDefaultValueTestEnumerable((c, e) => c.AddRange(e), (c, e) => c.AddRange(e), EntityState.Added);
+ await TrackMultipleEntitiesDefaultValueTestEnumerable((c, e) => c.AddRange(e), (c, e) => c.AddRange(e), EntityState.Added);
}
[Fact]
- public void Can_add_multiple_existing_entities_with_default_values_to_context_to_be_attached_Enumerable_graph()
+ public async Task Can_add_multiple_new_entities_with_default_values_to_context_Enumerable_graph_async()
{
- TrackMultipleEntitiesDefaultValueTestEnumerable((c, e) => c.AttachRange(e), (c, e) => c.AttachRange(e), EntityState.Unchanged);
+ await TrackMultipleEntitiesDefaultValueTestEnumerable((c, e) => c.AddRangeAsync(e), (c, e) => c.AddRangeAsync(e), EntityState.Added);
}
[Fact]
- public void Can_add_multiple_existing_entities_with_default_values_to_context_to_be_updated_Enumerable_graph()
+ public async Task Can_add_multiple_existing_entities_with_default_values_to_context_to_be_attached_Enumerable_graph()
{
- TrackMultipleEntitiesDefaultValueTestEnumerable((c, e) => c.UpdateRange(e), (c, e) => c.UpdateRange(e), EntityState.Modified);
+ await TrackMultipleEntitiesDefaultValueTestEnumerable((c, e) => c.AttachRange(e), (c, e) => c.AttachRange(e), EntityState.Unchanged);
}
- // Issue #3890
- private static void TrackMultipleEntitiesDefaultValueTestEnumerable(
+ [Fact]
+ public async Task Can_add_multiple_existing_entities_with_default_values_to_context_to_be_updated_Enumerable_graph()
+ {
+ await TrackMultipleEntitiesDefaultValueTestEnumerable((c, e) => c.UpdateRange(e), (c, e) => c.UpdateRange(e), EntityState.Modified);
+ }
+
+ private static Task TrackMultipleEntitiesDefaultValueTestEnumerable(
Action> categoryAdder,
Action> productAdder, EntityState expectedState)
+ => TrackMultipleEntitiesDefaultValueTestEnumerable(
+ (c, e) =>
+ {
+ categoryAdder(c, e);
+ return Task.FromResult(0);
+ },
+ (c, e) =>
+ {
+ productAdder(c, e);
+ return Task.FromResult(0);
+ },
+ expectedState);
+
+ // Issue #3890
+ private static async Task TrackMultipleEntitiesDefaultValueTestEnumerable(
+ Func, Task> categoryAdder,
+ Func, Task> productAdder, EntityState expectedState)
{
using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
{
var category1 = new Category { Id = 0, Name = "Beverages" };
var product1 = new Product { Id = 0, Name = "Marmite", Price = 7.99m };
- categoryAdder(context, new List { category1 });
- productAdder(context, new List { product1 });
+ await categoryAdder(context, new List { category1 });
+ await productAdder(context, new List { product1 });
Assert.Same(category1, context.Entry(category1).Entity);
Assert.Same(product1, context.Entry(product1).Entity);
@@ -860,6 +1015,17 @@ public void Can_add_no_new_entities_to_context_Enumerable_graph()
TrackNoEntitiesTestEnumerable((c, e) => c.AddRange(e), (c, e) => c.AddRange(e));
}
+ [Fact]
+ public async Task Can_add_no_new_entities_to_context_Enumerable_graph_async()
+ {
+ using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
+ {
+ await context.AddRangeAsync(new HashSet());
+ await context.AddRangeAsync(new HashSet());
+ Assert.Empty(context.ChangeTracker.Entries());
+ }
+ }
+
[Fact]
public void Can_add_no_existing_entities_to_context_to_be_attached_Enumerable_graph()
{
@@ -884,21 +1050,27 @@ private static void TrackNoEntitiesTestEnumerable(
}
}
- [Fact]
- public void Can_add_new_entities_to_context_with_key_generation_graph()
- {
- TrackEntitiesWithKeyGenerationTest((c, e) => c.Add(e).Entity);
- }
-
- private static void TrackEntitiesWithKeyGenerationTest(Func adder)
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task Can_add_new_entities_to_context_with_key_generation_graph(bool async)
{
using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
{
var gu1 = new TheGu { ShirtColor = "Red" };
var gu2 = new TheGu { ShirtColor = "Still Red" };
- Assert.Same(gu1, adder(context, gu1));
- Assert.Same(gu2, adder(context, gu2));
+ if (async)
+ {
+ Assert.Same(gu1, (await context.AddAsync(gu1)).Entity);
+ Assert.Same(gu2, (await context.AddAsync(gu2)).Entity);
+ }
+ else
+ {
+ Assert.Same(gu1, context.Add(gu1).Entity);
+ Assert.Same(gu2, context.Add(gu2).Entity);
+ }
+
Assert.NotEqual(default(Guid), gu1.Id);
Assert.NotEqual(default(Guid), gu2.Id);
Assert.NotEqual(gu1.Id, gu2.Id);
@@ -914,46 +1086,71 @@ private static void TrackEntitiesWithKeyGenerationTest(Func c.Remove(e), EntityState.Detached, EntityState.Deleted);
- ChangeStateWithMethod((c, e) => c.Remove(e), EntityState.Unchanged, EntityState.Deleted);
- ChangeStateWithMethod((c, e) => c.Remove(e), EntityState.Deleted, EntityState.Deleted);
- ChangeStateWithMethod((c, e) => c.Remove(e), EntityState.Modified, EntityState.Deleted);
- ChangeStateWithMethod((c, e) => c.Remove(e), EntityState.Added, EntityState.Detached);
+ await ChangeStateWithMethod((c, e) => c.Remove(e), EntityState.Detached, EntityState.Deleted);
+ await ChangeStateWithMethod((c, e) => c.Remove(e), EntityState.Unchanged, EntityState.Deleted);
+ await ChangeStateWithMethod((c, e) => c.Remove(e), EntityState.Deleted, EntityState.Deleted);
+ await ChangeStateWithMethod((c, e) => c.Remove(e), EntityState.Modified, EntityState.Deleted);
+ await ChangeStateWithMethod((c, e) => c.Remove(e), EntityState.Added, EntityState.Detached);
}
[Fact]
- public void Can_use_graph_Add_to_change_entity_state()
+ public async Task Can_use_graph_Add_to_change_entity_state()
{
- ChangeStateWithMethod((c, e) => c.Add(e), EntityState.Detached, EntityState.Added);
- ChangeStateWithMethod((c, e) => c.Add(e), EntityState.Unchanged, EntityState.Added);
- ChangeStateWithMethod((c, e) => c.Add(e), EntityState.Deleted, EntityState.Added);
- ChangeStateWithMethod((c, e) => c.Add(e), EntityState.Modified, EntityState.Added);
- ChangeStateWithMethod((c, e) => c.Add(e), EntityState.Added, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Add(e), EntityState.Detached, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Add(e), EntityState.Unchanged, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Add(e), EntityState.Deleted, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Add(e), EntityState.Modified, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Add(e), EntityState.Added, EntityState.Added);
}
[Fact]
- public void Can_use_graph_Attach_to_change_entity_state()
+ public async Task Can_use_graph_Add_to_change_entity_state_async()
{
- ChangeStateWithMethod((c, e) => c.Attach(e), EntityState.Detached, EntityState.Unchanged);
- ChangeStateWithMethod((c, e) => c.Attach(e), EntityState.Unchanged, EntityState.Unchanged);
- ChangeStateWithMethod((c, e) => c.Attach(e), EntityState.Deleted, EntityState.Unchanged);
- ChangeStateWithMethod((c, e) => c.Attach(e), EntityState.Modified, EntityState.Unchanged);
- ChangeStateWithMethod((c, e) => c.Attach(e), EntityState.Added, EntityState.Unchanged);
+ await ChangeStateWithMethod((c, e) => c.AddAsync(e), EntityState.Detached, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.AddAsync(e), EntityState.Unchanged, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.AddAsync(e), EntityState.Deleted, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.AddAsync(e), EntityState.Modified, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.AddAsync(e), EntityState.Added, EntityState.Added);
}
[Fact]
- public void Can_use_graph_Update_to_change_entity_state()
+ public async Task Can_use_graph_Attach_to_change_entity_state()
{
- ChangeStateWithMethod((c, e) => c.Update(e), EntityState.Detached, EntityState.Modified);
- ChangeStateWithMethod((c, e) => c.Update(e), EntityState.Unchanged, EntityState.Modified);
- ChangeStateWithMethod((c, e) => c.Update(e), EntityState.Deleted, EntityState.Modified);
- ChangeStateWithMethod((c, e) => c.Update(e), EntityState.Modified, EntityState.Modified);
- ChangeStateWithMethod((c, e) => c.Update(e), EntityState.Added, EntityState.Modified);
+ await ChangeStateWithMethod((c, e) => c.Attach(e), EntityState.Detached, EntityState.Unchanged);
+ await ChangeStateWithMethod((c, e) => c.Attach(e), EntityState.Unchanged, EntityState.Unchanged);
+ await ChangeStateWithMethod((c, e) => c.Attach(e), EntityState.Deleted, EntityState.Unchanged);
+ await ChangeStateWithMethod((c, e) => c.Attach(e), EntityState.Modified, EntityState.Unchanged);
+ await ChangeStateWithMethod((c, e) => c.Attach(e), EntityState.Added, EntityState.Unchanged);
}
- private void ChangeStateWithMethod(Action action, EntityState initialState, EntityState expectedState)
+ [Fact]
+ public async Task Can_use_graph_Update_to_change_entity_state()
+ {
+ await ChangeStateWithMethod((c, e) => c.Update(e), EntityState.Detached, EntityState.Modified);
+ await ChangeStateWithMethod((c, e) => c.Update(e), EntityState.Unchanged, EntityState.Modified);
+ await ChangeStateWithMethod((c, e) => c.Update(e), EntityState.Deleted, EntityState.Modified);
+ await ChangeStateWithMethod((c, e) => c.Update(e), EntityState.Modified, EntityState.Modified);
+ await ChangeStateWithMethod((c, e) => c.Update(e), EntityState.Added, EntityState.Modified);
+ }
+
+ private Task ChangeStateWithMethod(
+ Action action,
+ EntityState initialState,
+ EntityState expectedState)
+ => ChangeStateWithMethod((c, e) =>
+ {
+ action(c, e);
+ return Task.FromResult(0);
+ },
+ initialState,
+ expectedState);
+
+ private async Task ChangeStateWithMethod(
+ Func action,
+ EntityState initialState,
+ EntityState expectedState)
{
using (var context = new EarlyLearningCenter(TestHelpers.Instance.CreateServiceProvider()))
{
@@ -962,7 +1159,7 @@ private void ChangeStateWithMethod(Action action, EntityState
entry.State = initialState;
- action(context, entity);
+ await action(context, entity);
Assert.Equal(expectedState, entry.State);
}
@@ -4813,7 +5010,7 @@ public void Auto_DetectChanges_for_Entry_can_be_switched_off(bool useGenericOver
}
[Fact]
- public void Add_Attach_Remove_Update_do_not_call_DetectChanges()
+ public async Task Add_Attach_Remove_Update_do_not_call_DetectChanges()
{
var provider = TestHelpers.Instance.CreateServiceProvider(new ServiceCollection().AddScoped());
using (var context = new ButTheHedgehogContext(provider))
@@ -4830,6 +5027,12 @@ public void Add_Attach_Remove_Update_do_not_call_DetectChanges()
context.AddRange(new Product { Id = id++, Name = "Little Hedgehogs" });
context.AddRange(new List { new Product { Id = id++, Name = "Little Hedgehogs" } });
context.AddRange(new List { new Product { Id = id++, Name = "Little Hedgehogs" } });
+ await context.AddAsync(new Product { Id = id++, Name = "Little Hedgehogs" });
+ await context.AddAsync((object)new Product { Id = id++, Name = "Little Hedgehogs" });
+ await context.AddRangeAsync(new Product { Id = id++, Name = "Little Hedgehogs" });
+ await context.AddRangeAsync(new Product { Id = id++, Name = "Little Hedgehogs" });
+ await context.AddRangeAsync(new List { new Product { Id = id++, Name = "Little Hedgehogs" } });
+ await context.AddRangeAsync(new List { new Product { Id = id++, Name = "Little Hedgehogs" } });
context.Attach(new Product { Id = id++, Name = "Little Hedgehogs" });
context.Attach((object)new Product { Id = id++, Name = "Little Hedgehogs" });
context.AttachRange(new Product { Id = id++, Name = "Little Hedgehogs" });
@@ -4901,9 +5104,10 @@ public async void It_throws_object_disposed_exception()
Assert.Throws(() => context.Remove(new object()));
Assert.Throws(() => context.SaveChanges());
await Assert.ThrowsAsync(() => context.SaveChangesAsync());
+ await Assert.ThrowsAsync(() => context.AddAsync(new object()));
var methodCount = typeof(DbContext).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Count();
- var expectedMethodCount = 27;
+ var expectedMethodCount = 31;
Assert.True(
methodCount == expectedMethodCount,
userMessage: $"Expected {expectedMethodCount} methods on DbContext but found {methodCount}. " +
diff --git a/test/Microsoft.EntityFrameworkCore.Tests/DbSetTest.cs b/test/Microsoft.EntityFrameworkCore.Tests/DbSetTest.cs
index c4562ffffc3..198bed1e2c9 100644
--- a/test/Microsoft.EntityFrameworkCore.Tests/DbSetTest.cs
+++ b/test/Microsoft.EntityFrameworkCore.Tests/DbSetTest.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
+using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Specification.Tests;
using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -15,32 +16,46 @@ namespace Microsoft.EntityFrameworkCore.Tests
public class DbSetTest
{
[Fact]
- public void Can_add_existing_entities_to_context_to_be_deleted()
+ public async Task Can_add_existing_entities_to_context_to_be_deleted()
{
- TrackEntitiesTest((c, e) => c.Remove(e), (c, e) => c.Remove(e), EntityState.Deleted);
+ await TrackEntitiesTest((c, e) => c.Remove(e), (c, e) => c.Remove(e), EntityState.Deleted);
}
[Fact]
- public void Can_add_new_entities_to_context_graph()
+ public async Task Can_add_new_entities_to_context_graph()
{
- TrackEntitiesTest((c, e) => c.Add(e), (c, e) => c.Add(e), EntityState.Added);
+ await TrackEntitiesTest((c, e) => c.Add(e), (c, e) => c.Add(e), EntityState.Added);
}
[Fact]
- public void Can_add_existing_entities_to_context_to_be_attached_graph()
+ public async Task Can_add_new_entities_to_context_graph_async()
{
- TrackEntitiesTest((c, e) => c.Attach(e), (c, e) => c.Attach(e), EntityState.Unchanged);
+ await TrackEntitiesTest((c, e) => c.AddAsync(e), (c, e) => c.AddAsync(e), EntityState.Added);
}
[Fact]
- public void Can_add_existing_entities_to_context_to_be_updated_graph()
+ public async Task Can_add_existing_entities_to_context_to_be_attached_graph()
{
- TrackEntitiesTest((c, e) => c.Update(e), (c, e) => c.Update(e), EntityState.Modified);
+ await TrackEntitiesTest((c, e) => c.Attach(e), (c, e) => c.Attach(e), EntityState.Unchanged);
}
- private static void TrackEntitiesTest(
+ [Fact]
+ public async Task Can_add_existing_entities_to_context_to_be_updated_graph()
+ {
+ await TrackEntitiesTest((c, e) => c.Update(e), (c, e) => c.Update(e), EntityState.Modified);
+ }
+
+ private static Task TrackEntitiesTest(
Func, Category, EntityEntry> categoryAdder,
Func, Product, EntityEntry> productAdder, EntityState expectedState)
+ => TrackEntitiesTest(
+ (c, e) => Task.FromResult(categoryAdder(c, e)),
+ (c, e) => Task.FromResult(productAdder(c, e)),
+ expectedState);
+
+ private static async Task TrackEntitiesTest(
+ Func, Category, Task>> categoryAdder,
+ Func, Product, Task>> productAdder, EntityState expectedState)
{
using (var context = new EarlyLearningCenter())
{
@@ -49,10 +64,10 @@ private static void TrackEntitiesTest(
var product1 = new Product { Id = 1, Name = "Marmite", Price = 7.99m };
var product2 = new Product { Id = 2, Name = "Bovril", Price = 4.99m };
- var categoryEntry1 = categoryAdder(context.Categories, category1);
- var categoryEntry2 = categoryAdder(context.Categories, category2);
- var productEntry1 = productAdder(context.Products, product1);
- var productEntry2 = productAdder(context.Products, product2);
+ var categoryEntry1 = await categoryAdder(context.Categories, category1);
+ var categoryEntry2 = await categoryAdder(context.Categories, category2);
+ var productEntry1 = await productAdder(context.Products, product1);
+ var productEntry2 = await productAdder(context.Products, product2);
Assert.Same(category1, categoryEntry1.Entity);
Assert.Same(category2, categoryEntry2.Entity);
@@ -77,32 +92,69 @@ private static void TrackEntitiesTest(
}
[Fact]
- public void Can_add_multiple_new_entities_to_set()
+ public async Task Can_add_multiple_new_entities_to_set()
+ {
+ await TrackMultipleEntitiesTest(
+ (c, e) => c.Categories.AddRange(e[0], e[1]),
+ (c, e) => c.Products.AddRange(e[0], e[1]),
+ EntityState.Added);
+ }
+
+ [Fact]
+ public async Task Can_add_multiple_new_entities_to_set_async()
{
- TrackMultipleEntitiesTest((c, e) => c.Categories.AddRange(e[0], e[1]), (c, e) => c.Products.AddRange(e[0], e[1]), EntityState.Added);
+ await TrackMultipleEntitiesTest(
+ (c, e) => c.Categories.AddRangeAsync(e[0], e[1]),
+ (c, e) => c.Products.AddRangeAsync(e[0], e[1]),
+ EntityState.Added);
}
[Fact]
- public void Can_add_multiple_existing_entities_to_set_to_be_attached()
+ public async Task Can_add_multiple_existing_entities_to_set_to_be_attached()
{
- TrackMultipleEntitiesTest((c, e) => c.Categories.AttachRange(e[0], e[1]), (c, e) => c.Products.AttachRange(e[0], e[1]), EntityState.Unchanged);
+ await TrackMultipleEntitiesTest(
+ (c, e) => c.Categories.AttachRange(e[0], e[1]),
+ (c, e) => c.Products.AttachRange(e[0], e[1]),
+ EntityState.Unchanged);
}
[Fact]
- public void Can_add_multiple_existing_entities_to_set_to_be_updated()
+ public async Task Can_add_multiple_existing_entities_to_set_to_be_updated()
{
- TrackMultipleEntitiesTest((c, e) => c.Categories.UpdateRange(e[0], e[1]), (c, e) => c.Products.UpdateRange(e[0], e[1]), EntityState.Modified);
+ await TrackMultipleEntitiesTest(
+ (c, e) => c.Categories.UpdateRange(e[0], e[1]),
+ (c, e) => c.Products.UpdateRange(e[0], e[1]),
+ EntityState.Modified);
}
[Fact]
- public void Can_add_multiple_existing_entities_to_set_to_be_deleted()
+ public async Task Can_add_multiple_existing_entities_to_set_to_be_deleted()
{
- TrackMultipleEntitiesTest((c, e) => c.Categories.RemoveRange(e[0], e[1]), (c, e) => c.Products.RemoveRange(e[0], e[1]), EntityState.Deleted);
+ await TrackMultipleEntitiesTest(
+ (c, e) => c.Categories.RemoveRange(e[0], e[1]),
+ (c, e) => c.Products.RemoveRange(e[0], e[1]),
+ EntityState.Deleted);
}
- private static void TrackMultipleEntitiesTest(
+ private static Task TrackMultipleEntitiesTest(
Action categoryAdder,
Action productAdder, EntityState expectedState)
+ => TrackMultipleEntitiesTest(
+ (c, e) =>
+ {
+ categoryAdder(c, e);
+ return Task.FromResult(0);
+ },
+ (c, e) =>
+ {
+ productAdder(c, e);
+ return Task.FromResult(0);
+ },
+ expectedState);
+
+ private static async Task TrackMultipleEntitiesTest(
+ Func categoryAdder,
+ Func productAdder, EntityState expectedState)
{
using (var context = new EarlyLearningCenter())
{
@@ -111,8 +163,8 @@ private static void TrackMultipleEntitiesTest(
var product1 = new Product { Id = 1, Name = "Marmite", Price = 7.99m };
var product2 = new Product { Id = 2, Name = "Bovril", Price = 4.99m };
- categoryAdder(context, new[] { category1, category2 });
- productAdder(context, new[] { product1, product2 });
+ await categoryAdder(context, new[] { category1, category2 });
+ await productAdder(context, new[] { product1, product2 });
Assert.Same(category1, context.Entry(category1).Entity);
Assert.Same(category2, context.Entry(category2).Entity);
@@ -137,6 +189,17 @@ public void Can_add_no_new_entities_to_set()
TrackNoEntitiesTest(c => c.Categories.AddRange(), c => c.Products.AddRange());
}
+ [Fact]
+ public async Task Can_add_no_new_entities_to_set_async()
+ {
+ using (var context = new EarlyLearningCenter())
+ {
+ await context.Categories.AddRangeAsync();
+ await context.Products.AddRangeAsync();
+ Assert.Empty(context.ChangeTracker.Entries());
+ }
+ }
+
[Fact]
public void Can_add_no_existing_entities_to_set_to_be_attached()
{
@@ -166,32 +229,69 @@ private static void TrackNoEntitiesTest(Action categoryAdde
}
[Fact]
- public void Can_add_multiple_existing_entities_to_set_to_be_deleted_Enumerable()
+ public async Task Can_add_multiple_existing_entities_to_set_to_be_deleted_Enumerable()
{
- TrackMultipleEntitiesTestEnumerable((c, e) => c.Categories.RemoveRange(e), (c, e) => c.Products.RemoveRange(e), EntityState.Deleted);
+ await TrackMultipleEntitiesTestEnumerable(
+ (c, e) => c.Categories.RemoveRange(e),
+ (c, e) => c.Products.RemoveRange(e),
+ EntityState.Deleted);
}
[Fact]
- public void Can_add_multiple_new_entities_to_set_Enumerable_graph()
+ public async Task Can_add_multiple_new_entities_to_set_Enumerable_graph()
{
- TrackMultipleEntitiesTestEnumerable((c, e) => c.Categories.AddRange(e), (c, e) => c.Products.AddRange(e), EntityState.Added);
+ await TrackMultipleEntitiesTestEnumerable(
+ (c, e) => c.Categories.AddRange(e),
+ (c, e) => c.Products.AddRange(e),
+ EntityState.Added);
}
[Fact]
- public void Can_add_multiple_existing_entities_to_set_to_be_attached_Enumerable_graph()
+ public async Task Can_add_multiple_new_entities_to_set_Enumerable_graph_async()
{
- TrackMultipleEntitiesTestEnumerable((c, e) => c.Categories.AttachRange(e), (c, e) => c.Products.AttachRange(e), EntityState.Unchanged);
+ await TrackMultipleEntitiesTestEnumerable(
+ (c, e) => c.Categories.AddRangeAsync(e),
+ (c, e) => c.Products.AddRangeAsync(e),
+ EntityState.Added);
}
[Fact]
- public void Can_add_multiple_existing_entities_to_set_to_be_updated_Enumerable_graph()
+ public async Task Can_add_multiple_existing_entities_to_set_to_be_attached_Enumerable_graph()
{
- TrackMultipleEntitiesTestEnumerable((c, e) => c.Categories.UpdateRange(e), (c, e) => c.Products.UpdateRange(e), EntityState.Modified);
+ await TrackMultipleEntitiesTestEnumerable(
+ (c, e) => c.Categories.AttachRange(e),
+ (c, e) => c.Products.AttachRange(e),
+ EntityState.Unchanged);
}
- private static void TrackMultipleEntitiesTestEnumerable(
+ [Fact]
+ public async Task Can_add_multiple_existing_entities_to_set_to_be_updated_Enumerable_graph()
+ {
+ await TrackMultipleEntitiesTestEnumerable(
+ (c, e) => c.Categories.UpdateRange(e),
+ (c, e) => c.Products.UpdateRange(e),
+ EntityState.Modified);
+ }
+
+ private static Task TrackMultipleEntitiesTestEnumerable(
Action> categoryAdder,
Action> productAdder, EntityState expectedState)
+ => TrackMultipleEntitiesTestEnumerable(
+ (c, e) =>
+ {
+ categoryAdder(c, e);
+ return Task.FromResult(0);
+ },
+ (c, e) =>
+ {
+ productAdder(c, e);
+ return Task.FromResult(0);
+ },
+ expectedState);
+
+ private static async Task TrackMultipleEntitiesTestEnumerable(
+ Func, Task> categoryAdder,
+ Func, Task> productAdder, EntityState expectedState)
{
using (var context = new EarlyLearningCenter())
{
@@ -200,8 +300,8 @@ private static void TrackMultipleEntitiesTestEnumerable(
var product1 = new Product { Id = 1, Name = "Marmite", Price = 7.99m };
var product2 = new Product { Id = 2, Name = "Bovril", Price = 4.99m };
- categoryAdder(context, new List { category1, category2 });
- productAdder(context, new List { product1, product2 });
+ await categoryAdder(context, new List { category1, category2 });
+ await productAdder(context, new List { product1, product2 });
Assert.Same(category1, context.Entry(category1).Entity);
Assert.Same(category2, context.Entry(category2).Entity);
@@ -232,6 +332,17 @@ public void Can_add_no_new_entities_to_set_Enumerable_graph()
TrackNoEntitiesTestEnumerable((c, e) => c.Categories.AddRange(e), (c, e) => c.Products.AddRange(e));
}
+ [Fact]
+ public async Task Can_add_no_new_entities_to_set_Enumerable_graph_async()
+ {
+ using (var context = new EarlyLearningCenter())
+ {
+ await context.Categories.AddRangeAsync(new HashSet());
+ await context.Products.AddRangeAsync(new HashSet());
+ Assert.Empty(context.ChangeTracker.Entries());
+ }
+ }
+
[Fact]
public void Can_add_no_existing_entities_to_set_to_be_attached_Enumerable_graph()
{
@@ -257,46 +368,71 @@ private static void TrackNoEntitiesTestEnumerable(
}
[Fact]
- public void Can_use_Add_to_change_entity_state()
+ public async Task Can_use_Add_to_change_entity_state()
{
- ChangeStateWithMethod((c, e) => c.Categories.Add(e), EntityState.Detached, EntityState.Added);
- ChangeStateWithMethod((c, e) => c.Categories.Add(e), EntityState.Unchanged, EntityState.Added);
- ChangeStateWithMethod((c, e) => c.Categories.Add(e), EntityState.Deleted, EntityState.Added);
- ChangeStateWithMethod((c, e) => c.Categories.Add(e), EntityState.Modified, EntityState.Added);
- ChangeStateWithMethod((c, e) => c.Categories.Add(e), EntityState.Added, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Categories.Add(e), EntityState.Detached, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Categories.Add(e), EntityState.Unchanged, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Categories.Add(e), EntityState.Deleted, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Categories.Add(e), EntityState.Modified, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Categories.Add(e), EntityState.Added, EntityState.Added);
}
[Fact]
- public void Can_use_Attach_to_change_entity_state()
+ public async Task Can_use_Add_to_change_entity_state_async()
{
- ChangeStateWithMethod((c, e) => c.Categories.Attach(e), EntityState.Detached, EntityState.Unchanged);
- ChangeStateWithMethod((c, e) => c.Categories.Attach(e), EntityState.Unchanged, EntityState.Unchanged);
- ChangeStateWithMethod((c, e) => c.Categories.Attach(e), EntityState.Deleted, EntityState.Unchanged);
- ChangeStateWithMethod((c, e) => c.Categories.Attach(e), EntityState.Modified, EntityState.Unchanged);
- ChangeStateWithMethod((c, e) => c.Categories.Attach(e), EntityState.Added, EntityState.Unchanged);
+ await ChangeStateWithMethod((c, e) => c.Categories.AddAsync(e), EntityState.Detached, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Categories.AddAsync(e), EntityState.Unchanged, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Categories.AddAsync(e), EntityState.Deleted, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Categories.AddAsync(e), EntityState.Modified, EntityState.Added);
+ await ChangeStateWithMethod((c, e) => c.Categories.AddAsync(e), EntityState.Added, EntityState.Added);
}
[Fact]
- public void Can_use_Update_to_change_entity_state()
+ public async Task Can_use_Attach_to_change_entity_state()
{
- ChangeStateWithMethod((c, e) => c.Categories.Update(e), EntityState.Detached, EntityState.Modified);
- ChangeStateWithMethod((c, e) => c.Categories.Update(e), EntityState.Unchanged, EntityState.Modified);
- ChangeStateWithMethod((c, e) => c.Categories.Update(e), EntityState.Deleted, EntityState.Modified);
- ChangeStateWithMethod((c, e) => c.Categories.Update(e), EntityState.Modified, EntityState.Modified);
- ChangeStateWithMethod((c, e) => c.Categories.Update(e), EntityState.Added, EntityState.Modified);
+ await ChangeStateWithMethod((c, e) => c.Categories.Attach(e), EntityState.Detached, EntityState.Unchanged);
+ await ChangeStateWithMethod((c, e) => c.Categories.Attach(e), EntityState.Unchanged, EntityState.Unchanged);
+ await ChangeStateWithMethod((c, e) => c.Categories.Attach(e), EntityState.Deleted, EntityState.Unchanged);
+ await ChangeStateWithMethod((c, e) => c.Categories.Attach(e), EntityState.Modified, EntityState.Unchanged);
+ await ChangeStateWithMethod((c, e) => c.Categories.Attach(e), EntityState.Added, EntityState.Unchanged);
}
[Fact]
- public void Can_use_Remove_to_change_entity_state()
+ public async Task Can_use_Update_to_change_entity_state()
{
- ChangeStateWithMethod((c, e) => c.Categories.Remove(e), EntityState.Detached, EntityState.Deleted);
- ChangeStateWithMethod((c, e) => c.Categories.Remove(e), EntityState.Unchanged, EntityState.Deleted);
- ChangeStateWithMethod((c, e) => c.Categories.Remove(e), EntityState.Deleted, EntityState.Deleted);
- ChangeStateWithMethod((c, e) => c.Categories.Remove(e), EntityState.Modified, EntityState.Deleted);
- ChangeStateWithMethod((c, e) => c.Categories.Remove(e), EntityState.Added, EntityState.Detached);
+ await ChangeStateWithMethod((c, e) => c.Categories.Update(e), EntityState.Detached, EntityState.Modified);
+ await ChangeStateWithMethod((c, e) => c.Categories.Update(e), EntityState.Unchanged, EntityState.Modified);
+ await ChangeStateWithMethod((c, e) => c.Categories.Update(e), EntityState.Deleted, EntityState.Modified);
+ await ChangeStateWithMethod((c, e) => c.Categories.Update(e), EntityState.Modified, EntityState.Modified);
+ await ChangeStateWithMethod((c, e) => c.Categories.Update(e), EntityState.Added, EntityState.Modified);
}
- private void ChangeStateWithMethod(Action action, EntityState initialState, EntityState expectedState)
+ [Fact]
+ public async Task Can_use_Remove_to_change_entity_state()
+ {
+ await ChangeStateWithMethod((c, e) => c.Categories.Remove(e), EntityState.Detached, EntityState.Deleted);
+ await ChangeStateWithMethod((c, e) => c.Categories.Remove(e), EntityState.Unchanged, EntityState.Deleted);
+ await ChangeStateWithMethod((c, e) => c.Categories.Remove(e), EntityState.Deleted, EntityState.Deleted);
+ await ChangeStateWithMethod((c, e) => c.Categories.Remove(e), EntityState.Modified, EntityState.Deleted);
+ await ChangeStateWithMethod((c, e) => c.Categories.Remove(e), EntityState.Added, EntityState.Detached);
+ }
+
+ private Task ChangeStateWithMethod(
+ Action action,
+ EntityState initialState,
+ EntityState expectedState)
+ => ChangeStateWithMethod((c, e) =>
+ {
+ action(c, e);
+ return Task.FromResult(0);
+ },
+ initialState,
+ expectedState);
+
+ private async Task ChangeStateWithMethod(
+ Func action,
+ EntityState initialState,
+ EntityState expectedState)
{
using (var context = new EarlyLearningCenter())
{
@@ -305,27 +441,33 @@ private void ChangeStateWithMethod(Action action,
entry.State = initialState;
- action(context, entity);
+ await action(context, entity);
Assert.Equal(expectedState, entry.State);
}
}
- [Fact]
- public void Can_add_new_entities_to_context_with_key_generation()
- {
- TrackEntitiesWithKeyGenerationTest((c, e) => c.Add(e).Entity);
- }
-
- private static void TrackEntitiesWithKeyGenerationTest(Func, TheGu, TheGu> adder)
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task Can_add_new_entities_to_context_with_key_generation(bool async)
{
using (var context = new EarlyLearningCenter())
{
var gu1 = new TheGu { ShirtColor = "Red" };
var gu2 = new TheGu { ShirtColor = "Still Red" };
- Assert.Same(gu1, adder(context.Gus, gu1));
- Assert.Same(gu2, adder(context.Gus, gu2));
+ if (async)
+ {
+ Assert.Same(gu1, (await context.Gus.AddAsync(gu1)).Entity);
+ Assert.Same(gu2, (await context.Gus.AddAsync(gu2)).Entity);
+ }
+ else
+ {
+ Assert.Same(gu1, context.Gus.Add(gu1).Entity);
+ Assert.Same(gu2, context.Gus.Add(gu2).Entity);
+ }
+
Assert.NotEqual(default(Guid), gu1.Id);
Assert.NotEqual(default(Guid), gu2.Id);
Assert.NotEqual(gu1.Id, gu2.Id);