-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
InMemory DbContext InvalidOperationException With Two DbContext Instances #19732
Comments
Yes. When non using proxies there is no alternative, since the entity itself has no idea if it is being tracked or not. However, for proxies it might make sense to do this--we did something similar in EF6. |
@ajcvickers Just to note: I am using lazy loading proxies on the InMemory context here. |
@Cooksauce Yes. If we make the change I suggested then the second call to Find will return a new instance because the original entity instance will no longer being tracked by the first context. |
@ajcvickers I should be able to get around this by looping through the change tracker and detaching all of the tracked entities before disposing the context, yes? When trying that, I run into another issue. When I try to detach an entity, I get exceptions about severing relationships as if I'm deleting them? Why is EF treating an entity detachment as if it's being deleted? Is that by design? Method here: public static void ClearCache<TEntity>(this MyContext context, IEnumerable<TEntity> selector)
{
if (selector is null)
throw new ArgumentNullException(nameof(selector));
var entries = context.ChangeTracker.Entries()
.Where(entry => selector
.Any(entity => entry.Entity.Equals(entity)));
foreach (var entry in entries)
{
entry.State = EntityState.Detached;
}
}
public static void ClearCache(this MyContext context)
{
ClearCache(context, context.ChangeTracker.Entries().Select(entry => entry.Entity));
} Stacktrace
|
@Cooksauce Why are you trying to reuse the first context instance? |
@ajcvickers Main reason is because this is all in an |
Maybe more context will help: I have a bulk import service which performs bulk imports outside of EF (for performance reasons), but is able to utilize an EF transaction & I'm trying to write some integration tests which are all in memory; making use of the InMemory provider. This works and runs as expected when using as production with Npgsql provider Obviously, this situation isn't explicitly supported by the EF Core team. But it's very close to working very well for it. I think the InMemory provider could be easily improved in some areas to more closely mimic a real database provider. If I had an Npgsql DbContext which was tracking entities, and I looped through the change tracker and detached all of those entities, would it delete them from the database? If not, then the InMemory provider should also not treat that as if I'm deleting them. Reduced Interface (for brevity)public interface IBulkImportService
{
/// <summary>
/// Performs an async bulk insert of an entity stream on the provided transaction which can be canceled.
/// If canceled in the middle of import, a <see cref="TaskCanceledException"/> will be thrown. It is up to the caller to Commit or Rollback the transaction.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="entities"></param>
/// <param name="transaction"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<int> BulkInsertAsync<TEntity>(IAsyncEnumerable<TEntity> entities, IDbContextTransaction transaction, CancellationToken cancellationToken) where TEntity : class;
} In Memory Implementationpublic class InMemoryBulkImportService : IBulkImportService
{
public async Task<int> BulkInsertAsync<TEntity>(IAsyncEnumerable<TEntity> entities, IDbContextTransaction transaction, CancellationToken cancellationToken) where TEntity : class
{
using (var db = NewInMemoryContext()) //Uses a shared InMemoryDatabaseRoot, resolved from DI container
{
await foreach (var entity in entities.WithCancellation(cancellationToken))
db.Attach(entity);
var changes = await db.SaveChangesAsync(cancellationToken);
return changes;
}
}
} Calling Service Under TestIBulkImportService _bulkImportService;
using (var dbOne = GetContext()) / / resolved from the DI container
{
using var transaction = await db1.Database.BeginTransactionAsync();
var author = await db1.Authors.FindAsync(1);
author.Book = new Book();
await db1.SaveChangesAsync();
IAsyncEnumerable<Word> wordsToAdd = CreateWords().Select(x =>
{
x.Book = author.Book;
x.BookId = author.Book.Id;
});
var count = await _bulkImportService.BulkInsertAsync(wordsToAdd, transaction, token);
author.Book.WordCount = count;
await db1.SaveChangesAsync();
await transaction.CommitAsync();
} Modelpublic class Author
{
public int Id { get; set; }
public virtual Book Book { get; set; }
}
public class Book
{
public int Id { get; set; }
public int WordCount { get; set; }
public virtual ICollection<Word> Words { get; set; }
}
public class Word
{
public int Id { get; set; }
public string Text { get; set; }
public virtual Book Book { get; set; }
public int BookId { get; set; }
} SolutionDon't explicitly dispose InMemory context, let |
@Cooksauce Thanks for the additional info. I'm not sure that the in-memory database is the correct choice for this. See the discussion in #18457. Transactions are one thing not supported by the in-memory database. |
Also, as it says in the exception message, you can disable the warning: optionsBuilder.ConfigureWarnings(e => e.Ignore(CoreEventId.DetachedLazyLoadingWarning)) This will turn attempting to load on on a disposed context into a no-op. |
@ajcvickers I'd rather avoid setting a broad ignore like that, since it could ignore real problems. Would you mind clarifying whether the InMemory provider should be treating entity detachments as a delete? (you can see in the stacktrace above it's is calling deletion methods CascadeDelete(..)) |
@ajcvickers Do you know the answer to this? I just need to know whether this is by design or a bug. |
@Cooksauce You may just need to change the cascade timing. Or you may be hitting something like this: #18982 If it isn't covered by these things, then please attach a small, runnable project or post a complete code listing that reproduces the behavior your are seeing. |
Thanks! |
Scenario
Integration testing using InMemory context
If I have two DbContext instances and move an entity from one to the other by calling
DbContext.Attach(..)
will the first instance still be tracking the entity? I'm getting an exception about lazy loading after disposal so I'm wondering if the second context (which gets disposed) is still hanging on to the reference?If not, any other insight would be greatly appreciated.
Example
Exception Message
Relevant Stacktrace
Further technical details
EF Core version: 3.0.0
Database provider: InMemory
Target framework: .NET Core 3.0
The text was updated successfully, but these errors were encountered: