Aracılığıyla paylaş


İşlem Kullanımı

İşlemler, birkaç veritabanı işleminin atomik bir şekilde işlenmesini sağlar. İşlem işlenirse, tüm işlemler veritabanına başarıyla uygulanır. İşlem geri alınırsa, işlemlerin hiçbiri veritabanına uygulanmaz.

Tavsiye

Bu makalenin örneğini GitHub'da görüntüleyebilirsiniz.

Varsayılan işlem davranışı

Varsayılan olarak, veritabanı sağlayıcısı işlemleri destekliyorsa, tek bir çağrıdaki SaveChanges tüm değişiklikler bir işleme uygulanır. Değişikliklerden herhangi biri başarısız olursa işlem geri alınır ve değişikliklerin hiçbiri veritabanına uygulanmaz. Bu, tamamen başarılı olması veya hata oluşması durumunda veritabanını değiştirmeden bırakmanın garanti olduğu anlamına gelir SaveChanges .

Çoğu uygulama için bu varsayılan davranış yeterlidir. İşlemleri yalnızca uygulama gereksinimleriniz gerekli görürse el ile denetlemeniz gerekir.

İşlemleri denetleme

API'yi DbContext.Database kullanarak işlemleri başlatabilir, işleyebilir ve geri alabilirsiniz. Aşağıdaki örnekte iki SaveChanges işlem ve tek bir işlemde yürütülen bir LINQ sorgusu gösterilmektedir:

using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();

try
{
    context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
    await context.SaveChangesAsync();

    context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
    await context.SaveChangesAsync();

    var blogs = await context.Blogs
        .OrderBy(b => b.Url)
        .ToListAsync();

    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    await transaction.CommitAsync();
}
catch (Exception)
{
    // TODO: Handle failure
}

Tüm ilişkisel veritabanı sağlayıcıları işlemleri desteklese de, başka sağlayıcı türleri işlem API'leri çağrıldığında hata verebilir veya no-op olabilir.

Uyarı

İşlemleri bu şekilde el ile denetlemek, örtük olarak çağrılan yeniden deneme yürütme stratejileriyle uyumlu değildir. Daha fazla bilgi için bkz. Bağlantı Dayanıklılığı .

Kayıt noktaları

Çağrıldığında SaveChanges ve bağlamda bir işlem zaten devam ediyorsa, EF veri kaydedilmeden önce otomatik olarak bir kaydetme noktası oluşturur. Kaydetme noktaları, bir hata oluşursa veya başka bir nedenle daha sonra geri dönülebilecek bir veritabanı işlemi içindeki noktalardır. Herhangi bir hatayla karşılaşırsa SaveChanges , işlemi otomatik olarak kayıt noktasına geri alır ve işlemi hiç başlatılmamış gibi aynı durumda bırakır. Bu, özellikle iyimser eşzamanlılık sorunları oluştuğunda sorunları düzeltmenize ve kaydetmeyi yeniden denemenize olanak tanır.

Uyarı

Kayıt noktaları, SQL Server'ın Birden Çok Etkin Sonuç Kümesi (MARS) ile uyumlu değildir. MARS etkin olmasa bile bağlantıda MARS etkinleştirildiğinde EF tarafından kayıt noktaları oluşturulmayacaktır. SaveChanges sırasında bir hata oluşursa, işlem bilinmeyen bir durumda bırakılabilir.

İşlemlerde olduğu gibi, kayıt noktalarını el ile yönetmek de mümkündür. Aşağıdaki örnek, bir işlem içinde bir kayıt noktası oluşturur ve hata durumunda bu kayıt noktasına geri döner:

using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();

try
{
    context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/dotnet/" });
    await context.SaveChangesAsync();

    await transaction.CreateSavepointAsync("BeforeMoreBlogs");

    context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/visualstudio/" });
    context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/aspnet/" });
    await context.SaveChangesAsync();

    await transaction.CommitAsync();
}
catch (Exception)
{
    // If a failure occurred, we rollback to the savepoint and can continue the transaction
    await transaction.RollbackToSavepointAsync("BeforeMoreBlogs");

    // TODO: Handle failure, possibly retry inserting blogs
}

Bağlamlar arası işlem

Bir işlemi birden çok bağlam örneğinde de paylaşabilirsiniz. Bu işlevsellik, DbTransaction ve DbConnection kullanımını gerektirdiğinden ve bunlar ilişkisel veritabanlarına özgü olduğundan, yalnızca ilişkisel veritabanı sağlayıcısı kullanılırken kullanılabilir.

Bir işlemi paylaşmak için bağlamların hem DbConnection hem de DbTransaction paylaşması gerekir.

Bağlantının dışarıdan sağlanmasına izin ver

Bir DbConnection paylaşımı, bir bağlantıyı oluştururken bağlama koyabilme yeteneğini gerektirir.

Dışarıdan DbConnection sağlanmasına izin vermenin en kolay yolu, bağlamı DbContext.OnConfiguring yöntemiyle yapılandırmayı durdurmak ve DbContextOptions öğelerini dışarıdan oluşturup, bağlam oluşturucusuna geçirmektir.

Tavsiye

DbContextOptionsBuilder bağlamı yapılandırmak için DbContext.OnConfiguring'de kullandığınız API'dir; şimdi DbContextOptions oluşturmak için onu dışarıda kullanacaksınız.

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    {
    }

    public DbSet<Blog> Blogs { get; set; }
}

Alternatif olarak, DbContext.OnConfiguring kullanmaya devam etmek ve ardından DbConnection içinde kullanılan kaydedilmiş bir DbContext.OnConfiguring'yi kabul etmektir.

public class BloggingContext : DbContext
{
    private DbConnection _connection;

    public BloggingContext(DbConnection connection)
    {
      _connection = connection;
    }

    public DbSet<Blog> Blogs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(_connection);
    }
}

Bağlantıyı ve işlemi paylaşma

Artık aynı bağlantıyı paylaşan birden çok bağlam örneği oluşturabilirsiniz. Ardından API'yi DbContext.Database.UseTransaction(DbTransaction) kullanarak her iki bağlamı da aynı işlemde listeleyin.

using var connection = new SqlConnection(connectionString);
var options = new DbContextOptionsBuilder<BloggingContext>()
    .UseSqlServer(connection)
    .Options;

using var context1 = new BloggingContext(options);
await using var transaction = await context1.Database.BeginTransactionAsync();
try
{
    context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
    await context1.SaveChangesAsync();

    using (var context2 = new BloggingContext(options))
    {
        await context2.Database.UseTransactionAsync(transaction.GetDbTransaction());

        var blogs = await context2.Blogs
            .OrderBy(b => b.Url)
            .ToListAsync();

        context2.Blogs.Add(new Blog { Url = "http://dot.net" });
        await context2.SaveChangesAsync();
    }

    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    await transaction.CommitAsync();
}
catch (Exception)
{
    // TODO: Handle failure
}

Harici DbTransactions kullanımı (yalnızca ilişkisel veritabanları)

İlişkisel veritabanına erişmek için birden çok veri erişim teknolojisi kullanıyorsanız, bu farklı teknolojiler tarafından gerçekleştirilen işlemler arasında bir işlem paylaşmak isteyebilirsiniz.

Aşağıdaki örnekte, aynı işlemde ADO.NET SqlClient işleminin ve Entity Framework Core işleminin nasıl gerçekleştirleneceği gösterilmektedir.

using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();

await using var transaction = (SqlTransaction)await connection.BeginTransactionAsync();
try
{
    // Run raw ADO.NET command in the transaction
    var command = connection.CreateCommand();
    command.Transaction = transaction;
    command.CommandText = "DELETE FROM dbo.Blogs";
    command.ExecuteNonQuery();

    // Run an EF Core command in the transaction
    var options = new DbContextOptionsBuilder<BloggingContext>()
        .UseSqlServer(connection)
        .Options;

    using (var context = new BloggingContext(options))
    {
        await context.Database.UseTransactionAsync(transaction);
        context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
        await context.SaveChangesAsync();
    }

    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    await transaction.CommitAsync();
}
catch (Exception)
{
    // TODO: Handle failure
}

System.Transactions kullanmak

Daha büyük bir kapsam genelinde koordine etmeniz gerekiyorsa ortam işlemlerini kullanabilirsiniz.

using (var scope = new TransactionScope(
           TransactionScopeOption.Required,
           new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    using var connection = new SqlConnection(connectionString);
    await connection.OpenAsync();

    try
    {
        // Run raw ADO.NET command in the transaction
        var command = connection.CreateCommand();
        command.CommandText = "DELETE FROM dbo.Blogs";
        await command.ExecuteNonQueryAsync();

        // Run an EF Core command in the transaction
        var options = new DbContextOptionsBuilder<BloggingContext>()
            .UseSqlServer(connection)
            .Options;

        using (var context = new BloggingContext(options))
        {
            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            await context.SaveChangesAsync();
        }

        // Commit transaction if all commands succeed, transaction will auto-rollback
        // when disposed if either commands fails
        scope.Complete();
    }
    catch (Exception)
    {
        // TODO: Handle failure
    }
}

Ayrıca, açık bir işleme de kaydolabilir.

using (var transaction = new CommittableTransaction(
           new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    var connection = new SqlConnection(connectionString);

    try
    {
        var options = new DbContextOptionsBuilder<BloggingContext>()
            .UseSqlServer(connection)
            .Options;

        using (var context = new BloggingContext(options))
        {
            await context.Database.OpenConnectionAsync();
            context.Database.EnlistTransaction(transaction);

            // Run raw ADO.NET command in the transaction
            var command = connection.CreateCommand();
            command.CommandText = "DELETE FROM dbo.Blogs";
            await command.ExecuteNonQueryAsync();

            // Run an EF Core command in the transaction
            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            await context.SaveChangesAsync();
            await context.Database.CloseConnectionAsync();
        }

        // Commit transaction if all commands succeed, transaction will auto-rollback
        // when disposed if either commands fails
        transaction.Commit();
    }
    catch (Exception)
    {
        // TODO: Handle failure
    }
}

Uyarı

Zaman uyumsuz API'ler kullanıyorsanız, ortam işleminin zaman uyumsuz çağrılar arasında akmasını sağlamak için oluşturucuda TransactionScope değerini belirttiğinizden emin olun.

ve ortam işlemleri hakkında TransactionScope daha fazla bilgi için bu belgelere bakın.

System.Transactions Sınırlamaları

  1. EF Core, System.Transactions desteğini uygulamak için veritabanı sağlayıcılarına dayanır. Bir sağlayıcı System.Transactions için destek uygulamazsa, bu API'lere yapılan çağrılar tamamen yoksayılabilir. SqlClient bunu destekler.

    Önemli

    İşlemleri yönetmek için api'ye güvenmeden önce API'nin sağlayıcınızla doğru şekilde davranıp davranmadığını test edin. Aksi takdirde veritabanı sağlayıcısının bakımcısına başvurmanız tavsiye edilir.

  2. System.Transactions içindeki dağıtılmış işlem desteği yalnızca Windows için .NET 7.0'a eklendi. Dağıtılmış işlemleri eski .NET sürümlerinde veya Windows dışı platformlarda kullanma girişimleri başarısız olur.

  3. TransactionScope zaman uyumsuz taahhüt/geri almayı desteklemez; bu da işlem tamamlanana kadar senkron biçimde yürütme iş parçacığını engellediği anlamına gelir.