Satu DbContext per permintaan web ... mengapa?


398

Saya telah membaca banyak artikel yang menjelaskan cara mengatur Entity Framework DbContextsehingga hanya satu yang dibuat dan digunakan per permintaan web HTTP menggunakan berbagai kerangka kerja DI.

Mengapa ini ide yang bagus? Apa keuntungan yang Anda dapatkan dengan menggunakan pendekatan ini? Apakah ada situasi tertentu di mana ini akan menjadi ide yang bagus? Adakah hal-hal yang dapat Anda lakukan dengan menggunakan teknik ini yang tidak dapat Anda lakukan saat instantiasi DbContexts per panggilan metode repositori?


9
Gueddari di mehdi.me/ambient-dbcontext-in-ef6 memanggil instance DbContext per metode repositori memanggil antipattern. Quote: "Dengan melakukan ini, Anda kehilangan hampir semua fitur yang disediakan Entity Framework melalui DbContext, termasuk cache tingkat 1, peta identitasnya, unit kerjanya, dan pelacakan perubahan serta kemampuan pemuatan malasnya . " Artikel luar biasa dengan saran bagus untuk menangani siklus hidup DBContexts. Pasti layak dibaca.
Christoph

Jawaban:


565

CATATAN: Jawaban ini berbicara tentang Kerangka Entitas DbContext, tetapi ini berlaku untuk segala jenis implementasi Unit Kerja, seperti LINQ untuk SQL DataContext, dan NHibernate ISession.

Mari kita mulai dengan menggemakan Ian: Memiliki satu DbContextuntuk seluruh aplikasi adalah Ide Buruk. Satu-satunya situasi di mana ini masuk akal adalah ketika Anda memiliki aplikasi single-threaded dan database yang hanya digunakan oleh instance aplikasi tunggal itu. Itu DbContextbukan thread-safe dan dan karena DbContextdata cache, itu akan segera basi. Ini akan membuat Anda dalam segala macam masalah ketika banyak pengguna / aplikasi bekerja pada basis data itu secara bersamaan (yang tentu saja sangat umum). Tapi saya berharap Anda sudah tahu itu dan hanya ingin tahu mengapa tidak hanya menyuntikkan contoh baru (yaitu dengan gaya hidup sementara) dari DbContextsiapa pun yang membutuhkannya. (untuk informasi lebih lanjut tentang mengapa satu DbContext-atau bahkan pada konteks per utas- buruk, baca jawaban ini ).

Mari saya mulai dengan mengatakan bahwa mendaftar DbContextsebagai transien dapat bekerja, tetapi biasanya Anda ingin memiliki satu contoh unit kerja dalam lingkup tertentu. Dalam aplikasi web, bisa jadi praktis untuk mendefinisikan cakupan seperti itu pada batas-batas permintaan web; dengan demikian gaya hidup Per Web Request. Ini memungkinkan Anda untuk membiarkan seluruh rangkaian objek beroperasi dalam konteks yang sama. Dengan kata lain, mereka beroperasi dalam transaksi bisnis yang sama.

Jika Anda tidak memiliki tujuan untuk memiliki serangkaian operasi yang beroperasi dalam konteks yang sama, dalam hal ini gaya hidup sementara baik-baik saja, tetapi ada beberapa hal yang perlu diperhatikan:

  • Karena setiap objek mendapatkan turunannya sendiri, setiap kelas yang mengubah keadaan sistem, perlu memanggil _context.SaveChanges()(jika tidak, perubahan akan hilang). Ini dapat menyulitkan kode Anda, dan menambahkan tanggung jawab kedua pada kode (tanggung jawab mengendalikan konteks), dan merupakan pelanggaran terhadap Prinsip Tanggung Jawab Tunggal .
  • Anda perlu memastikan bahwa entitas [dimuat dan disimpan oleh a DbContext] tidak pernah meninggalkan ruang lingkup kelas seperti itu, karena mereka tidak dapat digunakan dalam instance konteks kelas lain. Ini dapat sangat menyulitkan kode Anda, karena ketika Anda membutuhkan entitas itu, Anda perlu memuatnya lagi dengan id, yang juga dapat menyebabkan masalah kinerja.
  • Sejak DbContextditerapkan IDisposable, Anda mungkin masih ingin Buang semua instance yang dibuat. Jika Anda ingin melakukan ini, pada dasarnya Anda memiliki dua opsi. Anda harus membuangnya dalam metode yang sama setelah menelepon context.SaveChanges(), tetapi dalam hal itu logika bisnis mengambil kepemilikan atas objek yang diteruskan dari luar. Opsi kedua adalah Buang semua mesin virtual yang dibuat pada batas Permintaan Http, tetapi dalam hal ini Anda masih memerlukan semacam pelingkupan untuk memberi tahu wadah kapan mesin tersebut perlu dibuang.

Pilihan lain adalah tidak menyuntikkan DbContextsama sekali. Sebaliknya, Anda menyuntikkan DbContextFactoryyang dapat membuat contoh baru (saya dulu menggunakan pendekatan ini di masa lalu). Dengan cara ini logika bisnis mengontrol konteks secara eksplisit. Jika mungkin terlihat seperti ini:

public void SomeOperation()
{
    using (var context = this.contextFactory.CreateNew())
    {
        var entities = this.otherDependency.Operate(
            context, "some value");

        context.Entities.InsertOnSubmit(entities);

        context.SaveChanges();
    }
}

Sisi positifnya adalah Anda mengelola kehidupan DbContextsecara eksplisit dan mudah untuk mengaturnya. Ini juga memungkinkan Anda untuk menggunakan konteks tunggal dalam ruang lingkup tertentu, yang memiliki keuntungan jelas, seperti menjalankan kode dalam satu transaksi bisnis, dan dapat melewati entitas, karena mereka berasal dari sama DbContext.

The downside adalah bahwa Anda harus melewati DbContextdari metode ke metode (yang disebut Metode Injeksi). Perhatikan bahwa dalam beberapa hal solusi ini sama dengan pendekatan 'cakupan', tetapi sekarang ruang lingkup dikontrol dalam kode aplikasi itu sendiri (dan mungkin diulang berkali-kali). Ini adalah aplikasi yang bertanggung jawab untuk membuat dan membuang unit kerja. Karena DbContextini dibuat setelah grafik dependensi dibangun, Injeksi Konstruktor keluar dari gambar dan Anda perlu menunda untuk Metode Injeksi ketika Anda perlu meneruskan konteks dari satu kelas ke yang lain.

Metode Injeksi tidak terlalu buruk, tetapi ketika logika bisnis menjadi lebih kompleks, dan lebih banyak kelas terlibat, Anda harus meneruskannya dari metode ke metode dan kelas ke kelas, yang dapat menyulitkan banyak kode (saya telah melihat ini di masa lalu). Untuk aplikasi sederhana, pendekatan ini akan baik-baik saja.

Karena kelemahannya, pendekatan pabrik ini memiliki sistem yang lebih besar, pendekatan lain dapat berguna dan itu adalah di mana Anda membiarkan wadah atau kode infrastruktur / Komposisi Root mengelola unit kerja. Ini adalah gaya pertanyaan Anda.

Dengan membiarkan wadah dan / atau infrastruktur menangani ini, kode aplikasi Anda tidak tercemar dengan harus membuat, (secara opsional) komit dan Buang instance UoW, yang menjaga logika bisnis tetap sederhana dan bersih (hanya Satu Tanggung Jawab). Ada beberapa kesulitan dengan pendekatan ini. Misalnya, apakah Anda Berkomitmen dan Buang contoh tersebut?

Membuang unit kerja dapat dilakukan di akhir permintaan web. Namun banyak orang, secara keliru menganggap bahwa ini juga merupakan tempat untuk Komit unit kerja. Namun, pada saat itu dalam aplikasi, Anda tidak dapat menentukan dengan pasti bahwa unit kerja harus benar-benar berkomitmen. mis. Jika kode lapisan bisnis melempar pengecualian yang tertangkap lebih tinggi di callstack, Anda pasti tidak ingin Berkomitmen.

Solusi sebenarnya adalah lagi untuk secara eksplisit mengelola semacam ruang lingkup, tetapi kali ini lakukan di dalam Root Komposisi. Mengabstraksi semua logika bisnis di balik pola perintah / penangan , Anda akan dapat menulis dekorator yang dapat melilit setiap penangan perintah yang memungkinkan untuk melakukan ini. Contoh:

class TransactionalCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    readonly DbContext context;
    readonly ICommandHandler<TCommand> decorated;

    public TransactionCommandHandlerDecorator(
        DbContext context,
        ICommandHandler<TCommand> decorated)
    {
        this.context = context;
        this.decorated = decorated;
    }

    public void Handle(TCommand command)
    {
        this.decorated.Handle(command);

        context.SaveChanges();
    } 
}

Ini memastikan bahwa Anda hanya perlu menulis kode infrastruktur ini sekali saja. Setiap wadah DI padat memungkinkan Anda untuk mengatur dekorator seperti itu untuk melilit semua ICommandHandler<T>implementasi secara konsisten.


2
Wow - terima kasih atas jawabannya. Jika saya dapat membesarkan dua kali, saya akan. Di atas, Anda mengatakan "... tidak ada niat membiarkan seluruh rangkaian operasi beroperasi dalam konteks yang sama, dalam hal itu gaya hidup sementara baik-baik saja ...". Apa yang Anda maksud dengan "sementara", khususnya?
Andrew

14
@Andrew: 'Transient' adalah konsep Injeksi Ketergantungan, yang berarti bahwa jika sebuah layanan dikonfigurasikan untuk bersifat sementara, sebuah instance baru dari layanan dibuat setiap kali ia disuntikkan ke konsumen.
Steven

1
@ user981375: Untuk operasi CRUD Anda dapat membuat generik CreateCommand<TEnity>dan generik CreateCommandHandler<TEntity> : ICommandHandler<CreateCommand<TEntity>>(dan melakukan hal yang sama untuk Pembaruan, dan Hapus, dan memiliki satu GetByIdQuery<TEntity>permintaan). Namun, Anda harus bertanya pada diri sendiri apakah model ini adalah abstraksi yang berguna untuk operasi CRUD, atau apakah hanya menambah kompleksitas. Namun, Anda mungkin mendapat manfaat dari kemungkinan untuk dengan mudah menambahkan masalah lintas sektoral (melalui dekorator) menggunakan model ini. Anda harus mempertimbangkan pro dan kontra.
Steven

3
+1 Apakah Anda yakin saya menulis semua jawaban ini sebelum benar-benar membaca ini? BTW IMO Saya pikir ini penting bagi Anda untuk membahas Pembuangan DbContext di akhir (meskipun bagus bahwa Anda tetap agnostik wadah)
Ruben Bartelink

1
Tetapi Anda tidak meneruskan konteks ke kelas yang didekorasi, bagaimana kelas yang didekorasi dapat bekerja dengan konteks yang sama dengan yang diteruskan ke kelas TransactionCommandHandlerDecorator? misalnya jika kelas yang didekorasi adalah InsertCommandHandlerkelas, bagaimana ia dapat mendaftarkan operasi penyisipan ke konteks (DbContext dalam EF)?
Masoud

35

Ada dua rekomendasi yang bertentangan oleh microsoft dan banyak orang menggunakan DbContexts dengan cara yang sangat berbeda.

  1. Salah satu rekomendasi adalah "Buang DbContexts secepat mungkin" karena memiliki DbContext Alive menempati sumber daya berharga seperti koneksi db dll.
  2. Yang lain menyatakan bahwa One DbContext per request sangat direkomendasikan

Mereka bertentangan satu sama lain karena jika Permintaan Anda melakukan banyak hal yang tidak terkait dengan hal-hal Db, maka DbContext Anda disimpan tanpa alasan. Dengan demikian, membuang-buang DbContext Anda tetap hidup selagi permintaan Anda hanya menunggu hal-hal acak diselesaikan ...

Begitu banyak orang yang mengikuti aturan 1 memiliki DbContext mereka di dalam "pola Repositori" mereka dan membuat Instance baru per Database Query jadi X * DbContext per Request

Mereka hanya mendapatkan data dan membuang konteksnya secepat mungkin. Ini dianggap oleh banyak orang sebagai praktik yang dapat diterima. Meskipun ini memiliki manfaat menduduki sumber daya db Anda untuk waktu minimum, ini jelas mengorbankan semua permen UnitOfWork dan Caching yang ditawarkan EF.

Menjaga agar tetap hidup satu contoh multiguna DbContext memaksimalkan manfaat Caching tetapi karena DbContext tidak aman utas dan setiap permintaan Web berjalan di utas itu sendiri, DbContext per Permintaan adalah yang terpanjang yang dapat Anda pertahankan.

Jadi rekomendasi tim EF tentang penggunaan 1 Db Konteks per permintaan, itu jelas didasarkan pada kenyataan bahwa dalam Aplikasi Web, UnitOfWork kemungkinan besar akan berada dalam satu permintaan dan permintaan itu memiliki satu utas. Jadi satu DbContext per permintaan seperti manfaat ideal UnitOfWork dan Caching.

Tetapi dalam banyak kasus ini tidak benar. Saya mempertimbangkan untuk Logging UnitOfWork yang terpisah sehingga memiliki DbContext baru untuk Post-Request Logging di async threads sepenuhnya dapat diterima

Jadi Akhirnya ternyata masa hidup DbContext terbatas pada dua parameter ini. UnitOfWork and Thread


3
Dalam semua kewajaran, permintaan HTTP Anda harus diselesaikan agak cepat (beberapa ms). Jika mereka berjalan lebih lama dari itu, maka Anda mungkin ingin berpikir tentang melakukan pemrosesan latar belakang dengan sesuatu seperti penjadwal pekerjaan eksternal sehingga permintaan dapat segera kembali. Karena itu, arsitektur Anda seharusnya tidak terlalu bergantung pada HTTP. Secara keseluruhan, jawaban yang bagus.
naksir

34

Tidak ada satu jawaban pun di sini yang sebenarnya menjawab pertanyaan itu. OP tidak bertanya tentang desain DbContext tunggal / per-aplikasi, ia bertanya tentang desain permintaan per-web dan apa manfaat potensial yang bisa ada.

Saya akan merujuk http://mehdi.me/ambient-dbcontext-in-ef6/ karena Mehdi adalah sumber yang fantastis:

Kemungkinan peningkatan kinerja.

Setiap instance DbContext memelihara cache tingkat pertama dari semua entitas yang dimuat dari database. Setiap kali Anda menanyakan entitas dengan kunci utamanya, DbContext akan berusaha untuk mengambilnya dari cache tingkat pertama sebelum secara default meminta dari entitas. Bergantung pada pola kueri data Anda, menggunakan kembali DbContext yang sama di beberapa transaksi bisnis sekuensial dapat menghasilkan lebih sedikit kueri basis data yang dibuat berkat cache tingkat pertama DbContext.

Ini memungkinkan pemuatan malas.

Jika layanan Anda mengembalikan entitas yang persisten (sebagai lawan dari model tampilan yang dikembalikan atau jenis DTO lainnya) dan Anda ingin memanfaatkan pemuatan malas pada entitas tersebut, masa contoh DbContext dari mana entitas tersebut diambil harus melampaui ruang lingkup transaksi bisnis. Jika metode layanan membuang instance DbContext yang digunakan sebelum kembali, setiap upaya untuk malas memuat properti pada entitas yang dikembalikan akan gagal (apakah menggunakan lazy-loading atau tidak adalah ide yang baik adalah debat yang berbeda sama sekali yang tidak akan kita bahas. sini). Dalam contoh aplikasi web kami, lazy-loading biasanya digunakan dalam metode aksi pengontrol pada entitas yang dikembalikan oleh lapisan layanan terpisah. Dalam hal itu,

Perlu diingat ada kontra juga. Tautan itu berisi banyak sumber daya lain untuk dibaca tentang masalah ini.

Hanya memposting ini kalau-kalau orang lain tersandung pada pertanyaan ini dan tidak terserap dalam jawaban yang tidak benar-benar menjawab pertanyaan.


Tautan yang bagus! Mengelola DBContext secara eksplisit terlihat seperti pendekatan teraman.
aggsol

22

Saya cukup yakin itu karena DbContext sama sekali tidak aman. Jadi berbagi hal itu bukan ide yang bagus.


Apakah maksud Anda membagikannya di seluruh permintaan HTTP tidak pernah merupakan ide yang baik?
Andrew

2
Ya, Andrew, itulah yang dia maksudkan. Berbagi konteks hanya untuk aplikasi desktop utas tunggal.
Elisabeth

10
Bagaimana dengan berbagi konteks untuk satu permintaan. Jadi untuk satu permintaan kami dapat memiliki akses ke berbagai repositori dan melakukan transaksi di seluruh mereka dengan membagikan satu dan konteks yang sama?
Lyubomir Velchev

16

Satu hal yang tidak benar-benar dibahas dalam pertanyaan atau diskusi adalah kenyataan bahwa DbContext tidak dapat membatalkan perubahan. Anda dapat mengirim perubahan, tetapi Anda tidak dapat menghapus pohon perubahan, jadi jika Anda menggunakan konteks per permintaan, Anda kurang beruntung jika Anda perlu membuang perubahan untuk alasan apa pun.

Secara pribadi saya membuat instance DbContext ketika diperlukan - biasanya melekat pada komponen bisnis yang memiliki kemampuan untuk membuat ulang konteks jika diperlukan. Dengan cara itu saya memiliki kontrol atas proses, daripada memiliki satu contoh yang memaksa saya. Saya juga tidak harus membuat DbContext di setiap startup controller terlepas dari apakah itu benar-benar digunakan. Kemudian jika saya masih ingin memiliki per permintaan contoh, saya dapat membuatnya di CTOR (melalui DI atau secara manual) atau membuatnya sesuai kebutuhan dalam setiap metode pengontrol. Secara pribadi saya biasanya mengambil pendekatan yang terakhir untuk menghindari membuat contoh DbContext ketika mereka sebenarnya tidak diperlukan.

Itu tergantung dari sudut mana Anda melihatnya juga. Bagi saya contoh per permintaan tidak pernah masuk akal. Apakah DbContext benar-benar termasuk dalam Permintaan Http? Dari segi perilaku itulah tempat yang salah. Komponen bisnis Anda harus menciptakan konteks Anda, bukan permintaan Http. Kemudian Anda dapat membuat atau membuang komponen bisnis Anda sesuai kebutuhan dan tidak pernah khawatir tentang masa pakai konteks.


1
Ini adalah jawaban yang menarik dan saya setuju sebagian dengan Anda. Bagi saya, DbContext tidak harus dikaitkan dengan permintaan web, tetapi itu selalu diketik ke satu 'permintaan' seperti dalam: 'transaksi bisnis'. Dan ketika Anda mengikat konteks ke transaksi bisnis, pembatalan perubahan menjadi sangat aneh untuk dilakukan. Tetapi tidak memilikinya di batas permintaan web tidak berarti bahwa komponen bisnis (BC) harus menciptakan konteks; Saya pikir itu bukan tanggung jawab mereka. Sebagai gantinya, Anda dapat menerapkan pelingkupan menggunakan dekorator di sekitar BC Anda. Dengan cara ini Anda bahkan dapat mengubah pelingkupan tanpa perubahan kode apa pun.
Steven

1
Nah dalam hal itu injeksi ke dalam objek bisnis harus berurusan dengan manajemen seumur hidup. Dalam pandangan saya, objek bisnis memiliki konteks dan karenanya harus mengontrol masa pakai.
Rick Strahl

Secara singkat, apa yang Anda maksud ketika Anda mengatakan "kemampuan untuk menciptakan kembali konteks jika diperlukan"? apakah Anda memutar kemampuan rollback Anda sendiri? bisakah kamu menguraikan sedikit?
tntwyckoff

2
Secara pribadi, saya pikir agak sulit untuk memaksakan DbContext pada awalnya. Tidak ada jaminan bahwa Anda bahkan perlu menekan basis data. Mungkin Anda memanggil layanan pihak ke-3 yang mengubah status di sisi itu. Atau mungkin Anda benar-benar memiliki 2 atau 3 database yang sedang Anda kerjakan secara bersamaan. Anda tidak akan membuat banyak DbContext di awal kalau-kalau Anda akhirnya menggunakannya. Bisnis tahu data yang digunakannya, jadi itu miliknya. Cukup letakkan TransactionScope di awal jika diperlukan. Saya tidak berpikir semua panggilan perlu satu. Butuh sumber daya.
Daniel Lorenz

Itulah pertanyaan apakah Anda mengizinkan wadah untuk mengontrol masa pakai dbcontext yang kemudian mengontrol masa pakai kendali induk, terkadang terlalu berlebihan. Katakanlah jika saya ingin layanan tunggal disuntikkan ke pengendali saya maka saya tidak akan dapat menggunakan suntikan konstuktor karena sesuai permintaan semantik.
davidcarr

10

Saya setuju dengan pendapat sebelumnya. Adalah baik untuk mengatakan, bahwa jika Anda akan membagikan DbContext dalam aplikasi utas tunggal, Anda akan membutuhkan lebih banyak memori. Misalnya aplikasi web saya di Azure (satu contoh ekstra kecil) membutuhkan 150 MB memori lain dan saya memiliki sekitar 30 pengguna per jam. Aplikasi berbagi DBContext dalam Permintaan HTTP

Berikut ini adalah contoh nyata gambar: aplikasi telah digunakan pada jam 12 siang


Mungkin idenya adalah untuk membagikan konteks untuk satu permintaan. Jika kita mengakses berbagai repositori dan - kelas DBSet dan ingin agar operasi dengan mereka menjadi transaksional yang seharusnya menjadi solusi yang baik. Lihat proyek open source mvcforum.com Saya pikir itu dilakukan dalam penerapan pola desain Unit Kerja.
Lyubomir Velchev

3

Apa yang saya suka tentang itu adalah menyelaraskan unit-of-work (seperti yang dilihat pengguna - yaitu pengiriman halaman) dengan unit-of-work dalam arti ORM.

Oleh karena itu, Anda dapat membuat transaksional pengiriman seluruh halaman, yang tidak dapat Anda lakukan jika Anda mengekspos metode CRUD dengan masing-masing menciptakan konteks baru.


3

Alasan lain yang tidak masuk akal untuk tidak menggunakan DbContext singleton, bahkan dalam aplikasi pengguna tunggal berulir tunggal, adalah karena pola peta identitas yang digunakannya. Ini berarti bahwa setiap kali Anda mengambil data menggunakan kueri atau oleh id, itu akan membuat instance entitas yang diambil dalam cache. Lain kali Anda mengambil entitas yang sama, itu akan memberi Anda contoh cache entitas, jika tersedia, dengan modifikasi apa pun yang telah Anda lakukan dalam sesi yang sama. Ini diperlukan agar metode SaveChanges tidak berakhir dengan beberapa instance entitas yang berbeda dari catatan database yang sama; jika tidak, konteksnya harus menggabungkan data dari semua instance entitas tersebut.

Alasan yang menjadi masalah adalah DbContext singleton dapat menjadi bom waktu yang akhirnya bisa men-cache seluruh database + overhead dari objek .NET dalam memori.

Ada beberapa cara untuk mengatasi perilaku ini dengan hanya menggunakan kueri Linq dengan .NoTracking()metode ekstensi. Juga hari ini PC memiliki banyak RAM. Tapi biasanya itu bukan perilaku yang diinginkan.


Ini benar, tetapi Anda harus menganggap Pengumpul Sampah akan berfungsi, menjadikan masalah ini lebih virtual daripada yang sebenarnya.
tocqueville

3
Pengumpul sampah tidak akan mengumpulkan contoh objek apa pun yang dipegang oleh objek statis / tunggal aktif. Mereka akan berakhir di gen 2 tumpukan.
Dmitry S.

1

Masalah lain yang harus diperhatikan dengan Entity Framework secara spesifik adalah ketika menggunakan kombinasi membuat entitas baru, pemuatan malas, dan kemudian menggunakan entitas baru tersebut (dari konteks yang sama). Jika Anda tidak menggunakan IDbSet.Create (vs hanya baru), pemuatan malas pada entitas itu tidak berfungsi saat diambil dari konteks tempat ia dibuat. Contoh:

 public class Foo {
     public string Id {get; set; }
     public string BarId {get; set; }
     // lazy loaded relationship to bar
     public virtual Bar Bar { get; set;}
 }
 var foo = new Foo {
     Id = "foo id"
     BarId = "some existing bar id"
 };
 dbContext.Set<Foo>().Add(foo);
 dbContext.SaveChanges();

 // some other code, using the same context
 var foo = dbContext.Set<Foo>().Find("foo id");
 var barProp = foo.Bar.SomeBarProp; // fails with null reference even though we have BarId set.
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.