C# Entity Framework transaction of non-tracked entities

113 Views Asked by At

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.

1

There are 1 best solutions below

0
Pavel Kudrna On

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.