Bagikan melalui


Secara eksplisit Melacak Entitas

Setiap instans DbContext melacak perubahan yang dilakukan pada entitas. Entitas yang dilacak ini pada gilirannya mendorong perubahan ke database saat SaveChanges dipanggil.

Pelacakan perubahan Entity Framework Core (EF Core) berfungsi paling baik ketika instans yang sama DbContext digunakan untuk mengkueri entitas dan memperbaruinya dengan memanggil SaveChanges. Ini karena EF Core secara otomatis melacak status entitas yang ditanyakan dan kemudian mendeteksi setiap perubahan yang dibuat pada entitas ini saat SaveChanges dipanggil. Pendekatan ini tercakup dalam Pelacakan Perubahan di EF Core.

Tip

Dokumen ini mengasumsikan bahwa status entitas dan dasar-dasar pelacakan perubahan EF Core dipahami. Lihat Pelacakan Perubahan di EF Core untuk informasi selengkapnya tentang topik ini.

Tip

Anda dapat menjalankan dan men-debug ke semua kode dalam dokumen ini dengan mengunduh kode sampel dari GitHub.

Tip

Untuk kesederhanaan, dokumen ini menggunakan dan merujuk metode sinkron seperti SaveChanges daripada padanan asinkronnya seperti SaveChangesAsync. Memanggil dan menunggu metode asinkron dapat diganti kecuali dinyatakan lain.

Pendahuluan

Entitas dapat secara eksplisit "dilampirkan" sedih DbContext sehingga konteks kemudian melacak entitas tersebut. Ini terutama berguna ketika:

  1. Membuat entitas baru yang akan disisipkan ke dalam database.
  2. Melampirkan ulang entitas yang terputus yang sebelumnya dikueri oleh instans DbContext yang berbeda .

Yang pertama akan diperlukan oleh sebagian besar aplikasi, dan terutama ditangani oleh DbContext.Add metode .

Yang kedua hanya diperlukan oleh aplikasi yang mengubah entitas atau hubungannya saat entitas tidak dilacak. Misalnya, aplikasi web dapat mengirim entitas ke klien web tempat pengguna membuat perubahan dan mengirim entitas kembali. Entitas ini disebut sebagai "terputus" karena awalnya dikueri dari DbContext, tetapi kemudian terputus dari konteks tersebut ketika dikirim ke klien.

Aplikasi web sekarang harus melampirkan kembali entitas ini sehingga entitas tersebut kembali dilacak dan menunjukkan perubahan yang telah dibuat sedih yang dapat membuat pembaruan yang SaveChanges sesuai pada database. Ini terutama ditangani DbContext.Attach oleh metode dan DbContext.Update .

Tip

Melampirkan entitas ke instans DbContext yang sama dengan yang mereka kueri seharusnya tidak diperlukan. Jangan secara rutin melakukan kueri tanpa pelacakan lalu lampirkan entitas yang dikembalikan ke konteks yang sama. Ini akan lebih lambat daripada menggunakan kueri pelacakan, dan juga dapat mengakibatkan masalah seperti nilai properti bayangan yang hilang, sehingga lebih sulit untuk memperbaikinya.

Nilai kunci eksplisit versus yang dihasilkan

Secara default, properti bilangan bulat dan kunci GUID dikonfigurasi untuk menggunakan nilai kunci yang dihasilkan secara otomatis. Ini memiliki keuntungan utama untuk pelacakan perubahan: nilai kunci yang tidak diatur menunjukkan bahwa entitas adalah "baru". Dengan "baru", kami berarti belum dimasukkan ke dalam database.

Dua model digunakan di bagian berikut. Yang pertama dikonfigurasi untuk tidak menggunakan nilai kunci yang dihasilkan:

public class Blog
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int Id { get; set; }

    public string Name { get; set; }

    public IList<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int Id { get; set; }

    public string Title { get; set; }
    public string Content { get; set; }

    public int? BlogId { get; set; }
    public Blog Blog { get; set; }
}

Nilai kunci yang tidak dihasilkan (yaitu secara eksplisit diatur) ditampilkan terlebih dahulu dalam setiap contoh karena semuanya sangat eksplisit dan mudah diikuti. Ini kemudian diikuti dengan contoh di mana nilai kunci yang dihasilkan digunakan:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }

    public IList<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int? BlogId { get; set; }
    public Blog Blog { get; set; }
}

Perhatikan bahwa properti kunci dalam model ini tidak memerlukan konfigurasi tambahan di sini karena menggunakan nilai kunci yang dihasilkan adalah default untuk kunci bilangan bulat sederhana.

Menyisipkan entitas baru

Nilai kunci eksplisit

Entitas harus dilacak dalam status Added untuk disisipkan oleh SaveChanges. Entitas biasanya dimasukkan ke dalam status Ditambahkan dengan memanggil salah satu dari DbContext.Add, , DbContext.AddRangeDbContext.AddAsync, DbContext.AddRangeAsync, atau metode yang setara pada DbSet<TEntity>.

Tip

Semua metode ini bekerja dengan cara yang sama dalam konteks pelacakan perubahan. Lihat Fitur Pelacakan Perubahan Tambahan untuk informasi selengkapnya.

Misalnya, untuk mulai melacak blog baru:

context.Add(
    new Blog { Id = 1, Name = ".NET Blog", });

Memeriksa tampilan debug pelacak perubahan setelah panggilan ini menunjukkan bahwa konteks melacak entitas baru dalam Added status:

Blog {Id: 1} Added
  Id: 1 PK
  Name: '.NET Blog'
  Posts: []

Namun, metode Tambahkan tidak hanya berfungsi pada entitas individual. Mereka benar-benar mulai melacak seluruh grafik entitas terkait, menempatkan mereka semua ke Added status. Misalnya, untuk menyisipkan blog baru dan posting baru terkait:

context.Add(
    new Blog
    {
        Id = 1,
        Name = ".NET Blog",
        Posts =
        {
            new Post
            {
                Id = 1,
                Title = "Announcing the Release of EF Core 5.0",
                Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
            },
            new Post
            {
                Id = 2,
                Title = "Announcing F# 5",
                Content = "F# 5 is the latest version of F#, the functional programming language..."
            }
        }
    });

Konteksnya sekarang melacak semua entitas ini sebagai Added:

Blog {Id: 1} Added
  Id: 1 PK
  Name: '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Added
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Added
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}

Perhatikan bahwa nilai eksplisit telah ditetapkan untuk Id properti utama dalam contoh di atas. Ini karena model di sini telah dikonfigurasi untuk menggunakan nilai kunci yang ditetapkan secara eksplisit, daripada nilai kunci yang dihasilkan secara otomatis. Saat tidak menggunakan kunci yang dihasilkan, properti kunci harus diatur secara eksplisit sebelum memanggil Add. Nilai kunci ini kemudian disisipkan saat SaveChanges dipanggil. Misalnya, saat menggunakan SQLite:

-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Blogs" ("Id", "Name")
VALUES (@p0, @p1);

-- Executed DbCommand (0ms) [Parameters=[@p2='1' (DbType = String), @p3='1' (DbType = String), @p4='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p5='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("Id", "BlogId", "Content", "Title")
VALUES (@p2, @p3, @p4, @p5);

-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String), @p1='1' (DbType = String), @p2='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p3='Announcing F# 5' (Size = 15)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("Id", "BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2, @p3);

Semua entitas ini dilacak dalam Unchanged status setelah SaveChanges selesai, karena entitas ini sekarang ada di database:

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Unchanged
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}

Nilai kunci yang dihasilkan

Seperti disebutkan di atas, properti kunci bilangan bulat dan GUID dikonfigurasi untuk menggunakan nilai kunci yang dihasilkan secara otomatis secara default. Ini berarti bahwa aplikasi tidak boleh menetapkan nilai kunci apa pun secara eksplisit. Misalnya, untuk menyisipkan blog baru dan memposting semua dengan nilai kunci yang dihasilkan:

context.Add(
    new Blog
    {
        Name = ".NET Blog",
        Posts =
        {
            new Post
            {
                Title = "Announcing the Release of EF Core 5.0",
                Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
            },
            new Post
            {
                Title = "Announcing F# 5",
                Content = "F# 5 is the latest version of F#, the functional programming language..."
            }
        }
    });

Seperti halnya nilai kunci eksplisit, konteksnya sekarang melacak semua entitas ini sebagai Added:

Blog {Id: -2147482644} Added
  Id: -2147482644 PK Temporary
  Name: '.NET Blog'
  Posts: [{Id: -2147482637}, {Id: -2147482636}]
Post {Id: -2147482637} Added
  Id: -2147482637 PK Temporary
  BlogId: -2147482644 FK Temporary
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: -2147482644}
Post {Id: -2147482636} Added
  Id: -2147482636 PK Temporary
  BlogId: -2147482644 FK Temporary
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: -2147482644}

Perhatikan dalam hal ini bahwa nilai kunci sementara telah dihasilkan untuk setiap entitas. Nilai-nilai ini digunakan oleh EF Core sampai SaveChanges dipanggil, di mana nilai kunci nyata titik dibaca kembali dari database. Misalnya, saat menggunakan SQLite:

-- Executed DbCommand (0ms) [Parameters=[@p0='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Blogs" ("Name")
VALUES (@p0);
SELECT "Id"
FROM "Blogs"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();

-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p2='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p3='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p1, @p2, @p3);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();

-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p2='Announcing F# 5' (Size = 15)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();

Setelah SaveChanges selesai, semua entitas telah diperbarui dengan nilai kunci nyatanya dan dilacak dalam Unchanged status karena sekarang cocok dengan status dalam database:

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Unchanged
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}

Ini adalah status akhir yang sama persis dengan contoh sebelumnya yang menggunakan nilai kunci eksplisit.

Tip

Nilai kunci eksplisit masih dapat diatur bahkan saat menggunakan nilai kunci yang dihasilkan. EF Core kemudian akan mencoba menyisipkan menggunakan nilai kunci ini. Beberapa konfigurasi database, termasuk SQL Server dengan kolom Identitas, tidak mendukung sisipan tersebut dan akan melemparkan (lihat dokumen ini untuk solusi).

Melampirkan entitas yang ada

Nilai kunci eksplisit

Entitas yang dikembalikan dari kueri dilacak dalam status Unchanged . Status Unchanged berarti bahwa entitas belum dimodifikasi sejak dikueri. Entitas yang terputus, mungkin dikembalikan dari klien web dalam permintaan HTTP, dapat dimasukkan ke dalam status ini menggunakan DbContext.Attach, , DbContext.AttachRangeatau metode yang setara pada DbSet<TEntity>. Misalnya, untuk mulai melacak blog yang ada:

context.Attach(
    new Blog { Id = 1, Name = ".NET Blog", });

Catatan

Contohnya di sini adalah membuat entitas secara eksplisit dengan new untuk kesederhanaan. Biasanya instans entitas akan berasal dari sumber lain, seperti dideserialisasi dari klien, atau dibuat dari data di Pos HTTP.

Memeriksa tampilan debug pelacak perubahan setelah panggilan ini menunjukkan bahwa entitas dilacak dalam status Unchanged :

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Posts: []

Sama seperti Add, Attach sebenarnya menetapkan seluruh grafik entitas yang terhubung ke status Unchanged . Misalnya, untuk melampirkan blog yang ada dan postingan terkait yang ada:

context.Attach(
    new Blog
    {
        Id = 1,
        Name = ".NET Blog",
        Posts =
        {
            new Post
            {
                Id = 1,
                Title = "Announcing the Release of EF Core 5.0",
                Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
            },
            new Post
            {
                Id = 2,
                Title = "Announcing F# 5",
                Content = "F# 5 is the latest version of F#, the functional programming language..."
            }
        }
    });

Konteksnya sekarang melacak semua entitas ini sebagai Unchanged:

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Unchanged
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}

Memanggil SaveChanges pada saat ini tidak akan berpengaruh. Semua entitas ditandai sebagai Unchanged, sehingga tidak ada yang perlu diperbarui dalam database.

Nilai kunci yang dihasilkan

Seperti disebutkan di atas, properti kunci bilangan bulat dan GUID dikonfigurasi untuk menggunakan nilai kunci yang dihasilkan secara otomatis secara default. Ini memiliki keuntungan utama saat bekerja dengan entitas yang terputus: nilai kunci yang tidak diatur menunjukkan bahwa entitas belum dimasukkan ke dalam database. Ini memungkinkan pelacak perubahan untuk secara otomatis mendeteksi entitas baru dan menempatkannya dalam Added status. Misalnya, pertimbangkan untuk melampirkan grafik blog dan posting ini:

context.Attach(
    new Blog
    {
        Id = 1,
        Name = ".NET Blog",
        Posts =
        {
            new Post
            {
                Id = 1,
                Title = "Announcing the Release of EF Core 5.0",
                Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
            },
            new Post
            {
                Id = 2,
                Title = "Announcing F# 5",
                Content = "F# 5 is the latest version of F#, the functional programming language..."
            },
            new Post
            {
                Title = "Announcing .NET 5.0",
                Content = ".NET 5.0 includes many enhancements, including single file applications, more..."
            },
        }
    });

Blog memiliki nilai kunci 1, menunjukkan bahwa itu sudah ada dalam database. Dua postingan juga memiliki nilai kunci yang ditetapkan, tetapi yang ketiga tidak. EF Core akan melihat nilai kunci ini sebagai 0, default CLR untuk bilangan bulat. Ini menghasilkan EF Core yang menandai entitas baru sebagai Added alih-alih Unchanged:

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}, {Id: -2147482636}]
Post {Id: -2147482636} Added
  Id: -2147482636 PK Temporary
  BlogId: 1 FK
  Content: '.NET 5.0 includes many enhancements, including single file a...'
  Title: 'Announcing .NET 5.0'
  Blog: {Id: 1}
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Unchanged
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'

Memanggil SaveChanges pada saat ini tidak melakukan apa pun dengan Unchanged entitas, tetapi menyisipkan entitas baru ke dalam database. Misalnya, saat menggunakan SQLite:

-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET 5.0 includes many enhancements, including single file applications, more...' (Size = 80), @p2='Announcing .NET 5.0' (Size = 19)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();

Poin penting yang perlu diperhatikan di sini adalah bahwa, dengan nilai kunci yang dihasilkan, EF Core dapat secara otomatis membedakan baru dari entitas yang ada dalam grafik yang terputus. Singkatnya, saat menggunakan kunci yang dihasilkan, EF Core akan selalu menyisipkan entitas ketika entitas tersebut tidak memiliki set nilai kunci.

Memperbarui entitas yang ada

Nilai kunci eksplisit

DbContext.Update, DbContext.UpdateRange, dan metode DbSet<TEntity> yang setara bertingkah persis seperti Attach metode yang dijelaskan di atas, kecuali bahwa entitas dimasukkan ke dalam Modified alih-alih Unchanged status. Misalnya, untuk mulai melacak blog yang ada sebagai Modified:

context.Update(
    new Blog { Id = 1, Name = ".NET Blog", });

Memeriksa tampilan debug pelacak perubahan setelah panggilan ini menunjukkan bahwa konteks melacak entitas ini dalam Modified status:

Blog {Id: 1} Modified
  Id: 1 PK
  Name: '.NET Blog' Modified
  Posts: []

Sama seperti dengan Add dan Attach, Update sebenarnya menandai seluruh grafik entitas terkait sebagai Modified. Misalnya, untuk melampirkan blog yang ada dan posting yang ada terkait sebagai Modified:

context.Update(
    new Blog
    {
        Id = 1,
        Name = ".NET Blog",
        Posts =
        {
            new Post
            {
                Id = 1,
                Title = "Announcing the Release of EF Core 5.0",
                Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
            },
            new Post
            {
                Id = 2,
                Title = "Announcing F# 5",
                Content = "F# 5 is the latest version of F#, the functional programming language..."
            }
        }
    });

Konteksnya sekarang melacak semua entitas ini sebagai Modified:

Blog {Id: 1} Modified
  Id: 1 PK
  Name: '.NET Blog' Modified
  Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Modified
  Id: 1 PK
  BlogId: 1 FK Modified Originally <null>
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...' Modified
  Title: 'Announcing the Release of EF Core 5.0' Modified
  Blog: {Id: 1}
Post {Id: 2} Modified
  Id: 2 PK
  BlogId: 1 FK Modified Originally <null>
  Content: 'F# 5 is the latest version of F#, the functional programming...' Modified
  Title: 'Announcing F# 5' Modified
  Blog: {Id: 1}

Memanggil SaveChanges pada saat ini akan menyebabkan pembaruan dikirim ke database untuk semua entitas ini. Misalnya, saat menggunakan SQLite:

-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p3='1' (DbType = String), @p0='1' (DbType = String), @p1='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p2='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p3='2' (DbType = String), @p0='1' (DbType = String), @p1='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p2='Announcing F# 5' (Size = 15)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();

Nilai kunci yang dihasilkan

AttachSeperti halnya , nilai kunci yang dihasilkan memiliki manfaat utama yang sama untuk Update: nilai kunci yang tidak diatur menunjukkan bahwa entitas baru dan belum dimasukkan ke dalam database. AttachSeperti halnya , ini memungkinkan DbContext untuk secara otomatis mendeteksi entitas baru dan menempatkannya dalam Added status. Misalnya, pertimbangkan untuk memanggil Update dengan grafik blog dan posting ini:

context.Update(
    new Blog
    {
        Id = 1,
        Name = ".NET Blog",
        Posts =
        {
            new Post
            {
                Id = 1,
                Title = "Announcing the Release of EF Core 5.0",
                Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
            },
            new Post
            {
                Id = 2,
                Title = "Announcing F# 5",
                Content = "F# 5 is the latest version of F#, the functional programming language..."
            },
            new Post
            {
                Title = "Announcing .NET 5.0",
                Content = ".NET 5.0 includes many enhancements, including single file applications, more..."
            },
        }
    });

Seperti contohnya Attach , postingan tanpa nilai kunci terdeteksi sebagai baru dan diatur ke status Added . Entitas lain ditandai sebagai Modified:

Blog {Id: 1} Modified
  Id: 1 PK
  Name: '.NET Blog' Modified
  Posts: [{Id: 1}, {Id: 2}, {Id: -2147482633}]
Post {Id: -2147482633} Added
  Id: -2147482633 PK Temporary
  BlogId: 1 FK
  Content: '.NET 5.0 includes many enhancements, including single file a...'
  Title: 'Announcing .NET 5.0'
  Blog: {Id: 1}
Post {Id: 1} Modified
  Id: 1 PK
  BlogId: 1 FK Modified Originally <null>
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...' Modified
  Title: 'Announcing the Release of EF Core 5.0' Modified
  Blog: {Id: 1}
Post {Id: 2} Modified
  Id: 2 PK
  BlogId: 1 FK Modified Originally <null>
  Content: 'F# 5 is the latest version of F#, the functional programming...' Modified
  Title: 'Announcing F# 5' Modified
  Blog: {Id: 1}

Panggilan SaveChanges pada titik ini akan menyebabkan pembaruan dikirim ke database untuk semua entitas yang ada, sementara entitas baru disisipkan. Misalnya, saat menggunakan SQLite:

-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p3='1' (DbType = String), @p0='1' (DbType = String), @p1='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p2='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p3='2' (DbType = String), @p0='1' (DbType = String), @p1='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p2='Announcing F# 5' (Size = 15)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET 5.0 includes many enhancements, including single file applications, more...' (Size = 80), @p2='Announcing .NET 5.0' (Size = 19)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();

Ini adalah cara yang sangat mudah untuk menghasilkan pembaruan dan sisipan dari grafik yang terputus. Namun, hal ini mengakibatkan pembaruan atau penyisipan dikirim ke database untuk setiap properti dari setiap entitas yang dilacak, bahkan ketika beberapa nilai properti mungkin belum diubah. Jangan terlalu takut dengan ini; untuk banyak aplikasi dengan grafik kecil, ini bisa menjadi cara yang mudah dan pragmatis untuk menghasilkan pembaruan. Meskipun demikian, pola lain yang lebih kompleks terkadang dapat menghasilkan pembaruan yang lebih efisien, seperti yang dijelaskan dalam Resolusi Identitas di EF Core.

Menghapus entitas yang ada

Agar entitas dihapus oleh SaveChanges, entitas harus dilacak dalam status Deleted . Entitas biasanya dimasukkan ke Deleted dalam status dengan memanggil salah satu dari DbContext.Remove, DbContext.RemoveRange, atau metode yang setara pada DbSet<TEntity>. Misalnya, untuk menandai postingan yang ada sebagai Deleted:

context.Remove(
    new Post { Id = 2 });

Memeriksa tampilan debug pelacak perubahan setelah panggilan ini menunjukkan bahwa konteks melacak entitas dalam Deleted status:

Post {Id: 2} Deleted
  Id: 2 PK
  BlogId: <null> FK
  Content: <null>
  Title: <null>
  Blog: <null>

Entitas ini akan dihapus ketika SaveChanges dipanggil. Misalnya, saat menggunakan SQLite:

-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();

Setelah SaveChanges selesai, entitas yang dihapus dilepas dari DbContext karena tidak ada lagi dalam database. Oleh karena itu, tampilan debug kosong karena tidak ada entitas yang dilacak.

Menghapus entitas dependen/anak

Menghapus entitas dependen/anak dari grafik lebih mudah daripada menghapus entitas utama/induk. Lihat bagian berikutnya dan Mengubah Kunci Asing dan Navigasi untuk informasi selengkapnya.

Tidak biasa untuk memanggil Remove entitas yang dibuat dengan new. Selanjutnya, tidak seperti Add, Attach dan Update, jarang memanggil Remove entitas yang belum dilacak dalam status Unchanged atau Modified . Sebaliknya, biasanya untuk melacak satu entitas atau grafik entitas terkait, lalu memanggil Remove entitas yang harus dihapus. Grafik entitas terlacak ini biasanya dibuat oleh:

  1. Menjalankan kueri untuk entitas
  2. Attach Menggunakan metode atau Update pada grafik entitas yang terputus, seperti yang dijelaskan di bagian sebelumnya.

Misalnya, kode di bagian sebelumnya lebih cenderung mendapatkan postingan dari klien dan kemudian melakukan sesuatu seperti ini:

context.Attach(post);
context.Remove(post);

Ini berulah persis dengan cara yang sama seperti contoh sebelumnya, karena Remove panggilan pada entitas yang tidak dilacak menyebabkannya pertama kali dilampirkan dan kemudian ditandai sebagai Deleted.

Dalam contoh yang lebih realistis, grafik entitas pertama kali dilampirkan, dan kemudian beberapa entitas tersebut ditandai sebagai dihapus. Contohnya:

// Attach a blog and associated posts
context.Attach(blog);

// Mark one post as Deleted
context.Remove(blog.Posts[1]);

Semua entitas ditandai sebagai Unchanged, kecuali entitas yang Remove dipanggil:

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Deleted
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}

Entitas ini akan dihapus ketika SaveChanges dipanggil. Misalnya, saat menggunakan SQLite:

-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();

Setelah SaveChanges selesai, entitas yang dihapus dilepas dari DbContext karena tidak ada lagi dalam database. Entitas lain tetap dalam status Unchanged :

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Posts: [{Id: 1}]
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}

Menghapus entitas utama/induk

Setiap hubungan yang menghubungkan dua jenis entitas memiliki ujung utama atau induk, dan ujung dependen atau turunan. Entitas dependen/anak adalah entitas dengan properti kunci asing. Dalam hubungan satu-ke-banyak, prinsipal/induk berada di sisi "satu", dan dependen/anak berada di sisi "banyak". Lihat Hubungan untuk informasi selengkapnya.

Dalam contoh sebelumnya kami menghapus postingan, yang merupakan entitas dependen/anak dalam blog-posting hubungan satu-ke-banyak. Ini relatif mudah karena penghapusan entitas dependen/anak tidak berdampak pada entitas lain. Di sisi lain, menghapus entitas utama/induk juga harus berdampak pada entitas dependen/anak apa pun. Tidak melakukannya akan meninggalkan nilai kunci asing yang merujuk pada nilai kunci primer yang tidak ada lagi. Ini adalah status model yang tidak valid dan menghasilkan kesalahan batasan referensial di sebagian besar database.

Status model yang tidak valid ini dapat ditangani dengan dua cara:

  1. Mengatur nilai FK ke null. Ini menunjukkan bahwa dependen/anak tidak lagi terkait dengan prinsipal/induk apa pun. Ini adalah default untuk hubungan opsional di mana kunci asing harus nullable. Mengatur FK ke null tidak valid untuk hubungan yang diperlukan, di mana kunci asing biasanya tidak dapat diubah ke null.
  2. Menghapus dependen/anak. Ini adalah default untuk hubungan yang diperlukan, dan juga valid untuk hubungan opsional.

Lihat Mengubah Kunci Asing dan Navigasi untuk informasi terperinci tentang pelacakan perubahan dan hubungan.

Hubungan opsional

Properti Post.BlogId kunci asing dapat diubah ke null dalam model yang telah kami gunakan. Ini berarti hubungan bersifat opsional, dan karenanya perilaku default EF Core adalah mengatur BlogId properti kunci asing ke null ketika blog dihapus. Contohnya:

// Attach a blog and associated posts
context.Attach(blog);

// Mark the blog as deleted
context.Remove(blog);

Memeriksa tampilan debug pelacak perubahan setelah panggilan untuk Remove menunjukkan bahwa, seperti yang diharapkan, blog sekarang ditandai sebagai Deleted:

Blog {Id: 1} Deleted
  Id: 1 PK
  Name: '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Modified
  Id: 1 PK
  BlogId: <null> FK Modified Originally 1
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: <null>
Post {Id: 2} Modified
  Id: 2 PK
  BlogId: <null> FK Modified Originally 1
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: <null>

Lebih menarik lagi, semua posting terkait sekarang ditandai sebagai Modified. Ini karena properti kunci asing di setiap entitas telah diatur ke null. Memanggil SaveChanges memperbarui nilai kunci asing untuk setiap posting menjadi null dalam database, sebelum kemudian menghapus blog:

-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0=NULL], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0
WHERE "Id" = @p1;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p1='2' (DbType = String), @p0=NULL], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0
WHERE "Id" = @p1;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p2='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Blogs"
WHERE "Id" = @p2;
SELECT changes();

Setelah SaveChanges selesai, entitas yang dihapus dilepas dari DbContext karena tidak ada lagi dalam database. Entitas lain sekarang ditandai sebagai Unchanged dengan nilai kunci asing null, yang cocok dengan status database:

Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: <null> FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: <null>
Post {Id: 2} Unchanged
  Id: 2 PK
  BlogId: <null> FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: <null>

Hubungan yang diperlukan

Post.BlogId Jika properti kunci asing tidak dapat diubah ke null, maka hubungan antara blog dan posting menjadi "diperlukan". Dalam situasi ini, EF Core akan, secara default, menghapus entitas dependen/anak saat prinsipal/induk dihapus. Misalnya, menghapus blog dengan posting terkait seperti dalam contoh sebelumnya:

// Attach a blog and associated posts
context.Attach(blog);

// Mark the blog as deleted
context.Remove(blog);

Memeriksa tampilan debug pelacak perubahan mengikuti panggilan untuk Remove menunjukkan bahwa, seperti yang diharapkan, blog kembali ditandai sebagai Deleted:

Blog {Id: 1} Deleted
  Id: 1 PK
  Name: '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Deleted
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
Post {Id: 2} Deleted
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}

Lebih menarik dalam hal ini adalah bahwa semua posting terkait juga telah ditandai sebagai Deleted. Memanggil SaveChanges menyebabkan blog dan semua posting terkait dihapus dari database:

-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Blogs"
WHERE "Id" = @p1;

Setelah SaveChanges selesai, semua entitas yang dihapus dilepas dari DbContext karena entitas tersebut tidak lagi ada di database. Oleh karena itu, output dari tampilan debug kosong.

Catatan

Dokumen ini hanya menggores permukaan saat bekerja dengan hubungan di EF Core. Lihat Hubungan untuk informasi selengkapnya tentang hubungan pemodelan, dan Mengubah Kunci Asing dan Navigasi untuk informasi selengkapnya tentang memperbarui/menghapus entitas dependen/anak saat memanggil SaveChanges.

Pelacakan kustom dengan TrackGraph

ChangeTracker.TrackGraph berfungsi seperti Add, Attach dan Update kecuali menghasilkan panggilan balik untuk setiap instans entitas sebelum melacaknya. Ini memungkinkan logika kustom digunakan saat menentukan cara melacak entitas individu dalam grafik.

Misalnya, pertimbangkan aturan yang digunakan EF Core saat melacak entitas dengan nilai kunci yang dihasilkan: jika nilai kunci adalah nol, maka entitas baru dan harus dimasukkan. Mari kita perluas aturan ini untuk mengatakan apakah nilai kunci negatif, maka entitas harus dihapus. Ini memungkinkan kami mengubah nilai kunci utama dalam entitas grafik yang terputus untuk menandai entitas yang dihapus:

blog.Posts.Add(
    new Post
    {
        Title = "Announcing .NET 5.0",
        Content = ".NET 5.0 includes many enhancements, including single file applications, more..."
    }
);

var toDelete = blog.Posts.Single(e => e.Title == "Announcing F# 5");
toDelete.Id = -toDelete.Id;

Grafik yang terputus ini kemudian dapat dilacak menggunakan TrackGraph:

public static void UpdateBlog(Blog blog)
{
    using var context = new BlogsContext();

    context.ChangeTracker.TrackGraph(
        blog, node =>
        {
            var propertyEntry = node.Entry.Property("Id");
            var keyValue = (int)propertyEntry.CurrentValue;

            if (keyValue == 0)
            {
                node.Entry.State = EntityState.Added;
            }
            else if (keyValue < 0)
            {
                propertyEntry.CurrentValue = -keyValue;
                node.Entry.State = EntityState.Deleted;
            }
            else
            {
                node.Entry.State = EntityState.Modified;
            }

            Console.WriteLine($"Tracking {node.Entry.Metadata.DisplayName()} with key value {keyValue} as {node.Entry.State}");
        });

    context.SaveChanges();
}

Untuk setiap entitas dalam grafik, kode di atas memeriksa nilai kunci utama sebelum melacak entitas. Untuk nilai kunci unset (nol), kode melakukan apa yang biasanya akan dilakukan EF Core. Artinya, jika kunci tidak diatur, maka entitas ditandai sebagai Added. Jika kunci diatur dan nilainya non-negatif, maka entitas ditandai sebagai Modified. Namun, jika nilai kunci negatif ditemukan, nilai nyata dan non-negatifnya dipulihkan dan entitas dilacak sebagai Deleted.

Output dari menjalankan kode ini adalah:

Tracking Blog with key value 1 as Modified
Tracking Post with key value 1 as Modified
Tracking Post with key value -2 as Deleted
Tracking Post with key value 0 as Added

Catatan

Untuk kesederhanaan, kode ini mengasumsikan setiap entitas memiliki properti kunci primer bilangan bulat yang disebut Id. Ini dapat dikodifikasi menjadi kelas dasar abstrak atau antarmuka. Secara bergantian, properti atau properti kunci utama dapat diperoleh dari IEntityType metadata sedih sehingga kode ini akan berfungsi dengan semua jenis entitas.

TrackGraph memiliki dua kelebihan beban. Dalam kelebihan beban sederhana yang digunakan di atas, EF Core menentukan kapan harus berhenti melintasi grafik. Secara khusus, entitas tersebut berhenti mengunjungi entitas terkait baru dari entitas tertentu ketika entitas tersebut sudah dilacak, atau ketika panggilan balik tidak mulai melacak entitas.

Kelebihan beban lanjutan, ChangeTracker.TrackGraph<TState>(Object, TState, Func<EntityEntryGraphNode<TState>,Boolean>), memiliki panggilan balik yang mengembalikan bool. Jika panggilan balik mengembalikan false, maka traversal grafik berhenti, jika tidak, maka akan berlanjut. Perawatan harus dilakukan untuk menghindari perulangan tak terbatas saat menggunakan kelebihan beban ini.

Kelebihan muatan lanjutan juga memungkinkan status disediakan ke TrackGraph dan status ini kemudian diteruskan ke setiap panggilan balik.