I was unsuccessful in looking for some specifications of how entity instances obtained by AsNoTracking within a transaction are handled. A colleague of mine told me the transactions are DB layer things and AsNoTracking does not affect the behavior. I wish I had that confidence too. Are they in Entity Framework Core really? Or is it some kind of mix between DB transactions and back-end tracking? I need to use IsolationLevel.Serializable and reading some entity first which I am using later for some update. I am really interested in where I can afford to read the first entity like AsNoTracking.
According to my testing:
namespace test_ef_transaction_asnotracking;
internal class Program
{
static async Task Main(string[] args)
{
if (File.Exists("test.db"))
{
File.Delete("test.db");
}
var areStep1 = new AutoResetEvent(false);
var areStep2 = new AutoResetEvent(false);
var areStep3 = new AutoResetEvent(false);
var areStep4 = new AutoResetEvent(false);
var areStep5 = new AutoResetEvent(false);
var task1 = Task.Run(() =>
{
using (var dbContext = new MyDbContext())
{
dbContext.Database.EnsureCreated();
var p = new Parent() { Name = "ahoj" };
dbContext.Add(p);
dbContext.SaveChanges();
}
using (var dbContext = new MyDbContext())
{
//var t = dbContext.Database.BeginTransaction(System.Data.IsolationLevel.Serializable);
var p = dbContext.Parents.AsNoTracking().Single();
areStep1.Set();
areStep2.WaitOne();
p.Name = "vnější změna";
dbContext.Parents.Update(p);
dbContext.SaveChanges();
//t.Commit();
areStep3.Set();
}
});
var task2 = Task.Run(() =>
{
areStep1.WaitOne();
using (var dbContext = new MyDbContext())
{
var t = dbContext.Database.BeginTransaction(System.Data.IsolationLevel.Serializable);
var p = dbContext.Parents.AsNoTracking().Single();
var newP = new Parent() { Name = p.Name };
dbContext.Parents.Add(newP);
dbContext.SaveChanges();
areStep2.Set();
areStep3.WaitOne();
t.Commit();
var finalParents = dbContext.Parents.ToList();
foreach (var finalParent in finalParents)
{
Console.WriteLine(finalParent.Name);
}
}
});
await Task.WhenAll(task1, task2);
Console.WriteLine("...");
Console.ReadKey();
}
}
public sealed class MyDbContext : DbContext
{
public DbSet<Parent> Parents { get; private set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=test.db");
}
}
public class Parent
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow;
}
It is really matter of database, so the transaction even for untracked entities locking them fully in DB.