TIDAK menggunakan pola repositori, gunakan ORM apa adanya (EF)

95

Saya selalu menggunakan pola Repositori tetapi untuk proyek terbaru saya, saya ingin melihat apakah saya dapat menyempurnakan penggunaannya dan implementasi "Unit Kerja" saya. Semakin saya mulai menggali, saya mulai bertanya pada diri sendiri pertanyaan: "Apakah saya benar-benar membutuhkannya?"

Sekarang ini semua dimulai dengan beberapa komentar di Stackoverflow dengan jejak ke posting Ayende Rahien di blognya, dengan 2 spesifik,

Ini mungkin bisa dibicarakan selamanya dan itu tergantung pada aplikasi yang berbeda. Apa yang ingin saya ketahui,

  1. apakah pendekatan ini cocok untuk proyek Entity Framework?
  2. menggunakan pendekatan ini apakah logika bisnis masih berada di lapisan layanan, atau metode ekstensi (seperti yang dijelaskan di bawah, saya tahu, metode ekstensi menggunakan sesi NHib)?

Itu mudah dilakukan dengan menggunakan metode ekstensi. Bersih, sederhana, dan dapat digunakan kembali.

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

Dengan menggunakan pendekatan ini dan Ninjectsebagai DI, apakah saya perlu membuat Contextantarmuka dan memasukkannya ke dalam pengontrol saya?

Dejan.S
sumber

Jawaban:

103

Saya telah melalui banyak jalan dan membuat banyak implementasi repositori pada proyek yang berbeda dan ... Saya telah menyerah dan menyerah, inilah alasannya.

Pengkodean untuk pengecualian

Apakah Anda membuat kode untuk 1% kemungkinan database Anda akan berubah dari satu teknologi ke teknologi lainnya? Jika Anda berpikir tentang keadaan bisnis Anda di masa depan dan mengatakan ya, itu adalah kemungkinannya, maka a) mereka harus memiliki banyak uang untuk melakukan migrasi ke teknologi DB lain atau b) Anda memilih teknologi DB untuk bersenang-senang atau c ) Ada yang tidak beres dengan teknologi pertama yang Anda putuskan untuk digunakan.

Mengapa membuang sintaks LINQ yang kaya?

LINQ dan EF dikembangkan sehingga Anda dapat melakukan hal-hal yang rapi dengannya untuk membaca dan melintasi grafik objek. Membuat dan memelihara repositori yang dapat memberi Anda fleksibilitas yang sama untuk melakukannya adalah tugas yang mengerikan. Dalam pengalaman saya, setiap kali saya membuat repositori, saya SELALU mengalami kebocoran logika bisnis ke lapisan repositori untuk membuat kueri lebih berkinerja dan / atau mengurangi jumlah klik ke database.

Saya tidak ingin membuat metode untuk setiap permutasi kueri yang harus saya tulis. Saya mungkin juga menulis prosedur tersimpan. Saya tidak ingin GetOrder, GetOrderWithOrderItem, GetOrderWithOrderItemWithOrderActivity, GetOrderByUserId, dan seterusnya ... Saya hanya ingin mendapatkan entitas utama dan melintasi dan termasuk objek grafik seperti yang saya jadi tolong.

Sebagian besar contoh repositori adalah omong kosong

Kecuali jika Anda sedang mengembangkan sesuatu yang BENAR-BENAR sederhana seperti blog atau sesuatu yang pertanyaan Anda tidak akan pernah sesederhana 90% dari contoh yang Anda temukan di internet seputar pola repositori. Saya tidak bisa cukup menekankan ini! Ini adalah sesuatu yang seseorang harus merangkak melalui lumpur untuk mengetahuinya. Akan selalu ada satu kueri yang merusak repositori / solusi yang telah Anda buat, dan tidak sampai titik di mana Anda menebak-nebak diri Anda sendiri dan hutang / erosi teknis dimulai.

Jangan uji unit saya bro

Tetapi bagaimana dengan pengujian unit jika saya tidak memiliki repositori? Bagaimana saya akan mengejek? Sederhana saja. Mari kita lihat dari kedua sudut:

Tanpa repositori - Anda dapat memalsukan DbContextpenggunaan IDbContextatau beberapa trik lain tetapi kemudian Anda benar-benar menguji unit LINQ ke Objek dan bukan LINQ ke Entitas karena kueri ditentukan saat runtime ... Oke jadi itu tidak bagus! Jadi sekarang terserah pada uji integrasi untuk membahasnya.

Dengan repositori - Anda sekarang dapat memalsukan repositori Anda dan unit menguji lapisan di antaranya. Bagus kan? Sebenarnya tidak ... Dalam kasus di atas di mana Anda harus membocorkan logika ke lapisan repositori untuk membuat kueri lebih berkinerja dan / atau lebih sedikit klik ke database, bagaimana pengujian unit Anda dapat mencakupnya? Sekarang ada di lapisan repo dan Anda tidak ingin mengujinya, IQueryable<T>bukan? Jujur juga, pengujian unit Anda tidak akan mencakup kueri yang memiliki .Where()klausa 20 baris dan.Include()Banyak hubungan dan hits database lagi untuk melakukan semua hal lain ini, bla, bla, bla lagian karena kueri dihasilkan saat runtime. Juga karena Anda membuat repositori untuk menjaga ketekunan lapisan atas tidak diketahui, jika Anda sekarang ingin mengubah teknologi database, maaf pengujian unit Anda pasti tidak akan menjamin hasil yang sama pada waktu proses, kembali ke pengujian integrasi. Jadi seluruh isi repositori tampak aneh ..

2 sen

Kami sudah kehilangan banyak fungsi dan sintaks saat menggunakan EF daripada prosedur tersimpan biasa (penyisipan massal, penghapusan massal, CTE, dll.) Tetapi saya juga membuat kode dalam C # jadi saya tidak perlu mengetik biner. Kami menggunakan EF sehingga kami dapat memiliki kemungkinan untuk menggunakan penyedia yang berbeda dan untuk bekerja dengan grafik objek dengan cara terkait yang bagus di antara banyak hal. Abstraksi tertentu berguna dan ada juga yang tidak.

Ryan
sumber
17
Anda tidak membuat repositori untuk dapat mengujinya. Anda membuat repositori untuk dapat menguji unit logika bisnis . Adapun untuk memastikan bahwa kueri berfungsi: jauh lebih mudah untuk menulis pengujian integrasi untuk repositori karena mereka hanya berisi logika dan bukan bisnis apa pun.
jgauffin
16
Coding for the exception: Menggunakan repositori tidak dapat mengganti mesin database. Ini tentang memisahkan bisnis dari kegigihan.
jgauffin
2
Ini semua adalah poin yang sangat valid dengan banyak kebenaran di belakangnya. Namun apa yang kurang adalah realisasi bahwa LINQ berserakan tentang aplikasi alih-alih dibatasi ke lokasi yang konsisten membuat EF setara dengan panggilan SQL di halaman belakang kode. Setiap kueri LINQ adalah titik pemeliharaan potensial dalam suatu aplikasi, dan semakin banyak ada (dan semakin tersebar luas), semakin tinggi biaya pemeliharaan dan risikonya. Bayangkan menambahkan bendera 'dihapus' ke suatu entitas dan harus menemukan setiap tempat dalam aplikasi besar yang ditanyakan entitas, harus memodifikasi setiap ...
DVK
2
Saya pikir ini cupet dan letih. Mengapa Anda membocorkan logika ke dalam repo? Dan jika Anda melakukannya, mengapa itu penting? Ini adalah implementasi data. Yang kami lakukan hanyalah menutup LINQ dari kode lainnya dengan menyembunyikannya di belakang repo. Anda mengatakan jangan mengujinya, tetapi kemudian Anda menggunakan tidak bisa mengujinya sebagai argumen untuk tidak melakukannya. Jadi buatlah repo, jangan mengekspos IQuerable, dan jangan mengujinya. Setidaknya Anda dapat menguji semua yang lain dalam isolasi dari implementasi data. Dan peluang 1% untuk perubahan db itu masih sangat besar $ -wise.
Sinaesthetic
5
1 untuk jawaban ini. Saya menemukan bahwa kami benar-benar TIDAK memerlukan repositori dengan Entity Framework Core. Ini DbSetadalah repositori , dan DbContextmerupakan Unit Kerja . Mengapa menerapkan pola repositori ketika ORM sudah melakukannya untuk kami! Untuk pengujian, cukup ubah penyedia ke InMemory. Dan lakukan tes Anda! Itu didokumentasikan dengan baik di MSDN.
Mohammed Noureldin
49

Pola repositori adalah abstraksi . Tujuannya adalah untuk mengurangi kompleksitas dan membuat sisa kode tetap tidak peduli. Sebagai bonus, ini memungkinkan Anda menulis pengujian unit, bukan pengujian integrasi .

Masalahnya adalah bahwa banyak pengembang gagal memahami tujuan pola dan membuat repositori yang membocorkan informasi spesifik persistensi hingga pemanggil (biasanya dengan mengekspos IQueryable<T>). Dengan melakukan itu mereka tidak mendapatkan keuntungan atas penggunaan OR / M secara langsung.

Perbarui untuk menjawab jawaban lain

Pengkodean untuk pengecualian

Menggunakan repositori bukanlah tentang kemampuan untuk mengganti teknologi persistensi (yaitu mengubah database atau menggunakan layanan web, dll.). Ini tentang memisahkan logika bisnis dari ketekunan untuk mengurangi kompleksitas dan keterkaitan.

Tes unit vs tes integrasi

Anda tidak menulis pengujian unit untuk repositori. Titik.

Tetapi dengan memperkenalkan repositori (atau lapisan abstraksi lainnya antara persistensi dan bisnis), Anda dapat menulis pengujian unit untuk logika bisnis. yaitu Anda tidak perlu khawatir tentang pengujian Anda yang gagal karena database yang tidak dikonfigurasi dengan benar.

Adapun pertanyaannya. Jika Anda menggunakan LINQ, Anda juga harus memastikan bahwa kueri Anda berfungsi, seperti yang harus Anda lakukan dengan repositori. dan itu dilakukan dengan menggunakan uji integrasi.

Perbedaannya adalah jika Anda belum mencampurkan bisnis Anda dengan pernyataan LINQ, Anda dapat 100% yakin bahwa kode ketekunan Anda yang gagal dan bukan yang lainnya.

Jika Anda menganalisis pengujian Anda, Anda juga akan melihat bahwa pengujian tersebut jauh lebih bersih jika Anda tidak memiliki kekhawatiran yang campur aduk (yaitu logika LINQ + Bisnis)

Contoh repositori

Kebanyakan contoh adalah omong kosong. itu benar sekali. Namun, jika Anda mencari pola desain di Google, Anda akan menemukan banyak contoh jelek. Tidak ada alasan untuk menghindari penggunaan pola.

Membangun implementasi repositori yang benar sangatlah mudah. Faktanya, Anda hanya perlu mengikuti satu aturan:

Jangan menambahkan apapun ke dalam kelas repositori hingga saat Anda membutuhkannya

Banyak pembuat kode malas dan mencoba membuat repositori generik dan menggunakan kelas dasar dengan banyak metode yang mungkin mereka perlukan. YAGNI. Anda menulis kelas repositori sekali dan menyimpannya selama aplikasi hidup (bisa bertahun-tahun). Mengapa mengacaukannya dengan menjadi malas. Tetap bersih tanpa warisan kelas dasar. Ini akan membuatnya lebih mudah untuk dibaca dan dipelihara.

(Pernyataan di atas adalah pedoman dan bukan hukum. Kelas dasar bisa sangat termotivasi. Coba pikirkan sebelum menambahkannya, sehingga Anda menambahkannya untuk alasan yang benar)

Barang lama

Kesimpulan:

Jika Anda tidak keberatan memiliki pernyataan LINQ dalam kode bisnis Anda atau peduli tentang pengujian unit, saya tidak melihat alasan untuk tidak menggunakan Entity Framework secara langsung.

Memperbarui

Saya telah membuat blog tentang pola repositori dan apa arti sebenarnya dari "abstraksi": http://blog.gauffin.org/2013/01/repository-pattern-done-right/

Perbarui 2

Untuk tipe entitas tunggal dengan 20+ bidang, bagaimana Anda akan merancang metode kueri untuk mendukung kombinasi permutasi apa pun? Anda tidak ingin membatasi pencarian hanya dengan nama, bagaimana dengan pencarian dengan properti navigasi, daftar semua pesanan dengan item dengan kode harga tertentu, 3 level pencarian properti navigasi. Seluruh alasan IQueryableyang ditemukan adalah untuk dapat membuat kombinasi pencarian apapun terhadap database. Semuanya tampak hebat secara teori, tetapi kebutuhan pengguna menang di atas teori.

Sekali lagi: Entitas dengan 20+ kolom tidak dimodelkan dengan benar. Itu adalah entitas ALLAH. Memecahnya.

Saya tidak berdebat bahwa IQueryableitu tidak dibuat untuk quering. Saya mengatakan bahwa itu tidak tepat untuk lapisan abstraksi seperti pola Repositori karena itu bocor. Tidak ada penyedia LINQ To Sql yang 100% lengkap (seperti EF).

Mereka semua memiliki implementasi hal-hal spesifik seperti bagaimana menggunakan eager / lazy loading atau bagaimana melakukan pernyataan SQL "IN". Mengekspos IQueryabledi repositori memaksa pengguna untuk mengetahui semua hal itu. Dengan demikian seluruh upaya untuk mengabstraksi sumber data adalah kegagalan total. Anda hanya menambahkan kompleksitas tanpa mendapatkan keuntungan apa pun atas penggunaan OR / M secara langsung.

Terapkan pola Repositori dengan benar atau jangan gunakan sama sekali.

(Jika Anda benar-benar ingin menangani entitas besar, Anda dapat menggabungkan pola Repositori dengan pola Spesifikasi . Itu memberi Anda abstraksi lengkap yang juga dapat diuji.)

jgauffin.dll
sumber
6
Tidak mengekspos IQuerizable mengarah ke pencarian terbatas, dan orang-orang akhirnya membuat lebih banyak metode Get untuk berbagai jenis kueri, dan akhirnya membuat repositori menjadi lebih kompleks.
Akash Kava
3
Anda belum membahas masalah inti sama sekali: Mengekspos IQuer dapat melalui repositori bukanlah abstraksi yang lengkap.
jgauffin
1
Memiliki objek kueri yang berisi semua infrastruktur yang diperlukan untuk dieksekusi dengan sendirinya adalah cara yang tepat. Anda memberikannya bidang yang merupakan istilah pencarian dan mengembalikan daftar hasil. Di dalam QO, Anda dapat melakukan apa pun yang Anda inginkan. Dan itu adalah antarmuka, sehingga mudah diuji. Lihat posting saya di atas. Itu yang terbaik.
h. Alex
2
Secara pribadi, menurut saya masuk akal juga untuk mengimplementasikan antarmuka IQuerable <T> pada kelas Repositori, daripada mengekspos set yang mendasari di salah satu anggotanya.
dark_perfect
3
@ yat: Satu repo per akar agregat. Tapi imho itu bukan akar agregat dan agregat tabel tetapi hanya akar agregat dan agregat . Penyimpanan yang sebenarnya mungkin hanya menggunakan satu tabel atau banyak dari mereka, yaitu mungkin bukan pemetaan satu-satu antara setiap agregat dan tabel. Saya menggunakan repositori untuk mengurangi kompleksitas dan menghapus ketergantungan apa pun dari penyimpanan yang mendasarinya.
jgauffin
27

IMO baik Repositoryabstraksi maupun UnitOfWorkabstraksi memiliki tempat yang sangat berharga dalam perkembangan yang berarti. Orang akan berdebat tentang detail implementasi, tetapi seperti ada banyak cara untuk menguliti kucing, ada banyak cara untuk menerapkan abstraksi.

Pertanyaan Anda secara khusus digunakan atau tidak dan mengapa.

Karena Anda pasti menyadari bahwa Anda sudah memiliki kedua pola ini yang dibangun ke dalam Entity Framework, DbContextis the UnitOfWorkdan DbSetis the Repository. Anda biasanya tidak perlu menguji unit UnitOfWorkatau Repositorydirinya sendiri karena mereka hanya memfasilitasi antara kelas Anda dan implementasi akses data yang mendasarinya. Apa yang perlu Anda lakukan, lagi dan lagi, adalah mengejek kedua abstraksi ini saat unit menguji logika layanan Anda.

Anda dapat memalsukan, memalsukan, atau apa pun dengan pustaka eksternal yang menambahkan lapisan dependensi kode (yang tidak Anda kendalikan) antara logika yang melakukan pengujian dan logika yang sedang diuji.

Jadi poin kecilnya adalah memiliki abstraksi Anda sendiri UnitOfWorkdan Repositorymemberi Anda kontrol dan fleksibilitas maksimum saat mengejek pengujian unit Anda.

Semuanya baik-baik saja, tetapi bagi saya, kekuatan sebenarnya dari abstraksi ini adalah menyediakan cara sederhana untuk menerapkan teknik Pemrograman Berorientasi Aspek dan mematuhi prinsip SOLID .

Jadi, Anda memiliki IRepository:

public interface IRepository<T>
    where T : class
{
    T Add(T entity);
    void Delete(T entity);
    IQueryable<T> AsQueryable();
}

Dan implementasinya:

public class Repository<T> : IRepository<T>
    where T : class
{
    private readonly IDbSet<T> _dbSet;
    public Repository(PPContext context) 
    {
        _dbSet = context.Set<T>();
    }

    public T Add(T entity)
    { 
        return _dbSet.Add(entity); 
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity); 
    }

    public IQueryable<T> AsQueryable() 
    {
        return _dbSet.AsQueryable();
    }
}

Sejauh ini tidak ada yang luar biasa, tetapi sekarang kami ingin menambahkan beberapa logging - mudah dengan Dekorator logging .

public class RepositoryLoggerDecorator<T> : IRepository<T>
    where T : class
{
    Logger logger = LogManager.GetCurrentClassLogger();
    private readonly IRepository<T> _decorated;
    public RepositoryLoggerDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
        T added = _decorated.Add(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        return added;
    }

    public void Delete(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        _decorated.Delete(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable();
    }
}

Semua selesai dan tanpa perubahan pada kode kami yang ada . Ada banyak masalah lintas sektoral lain yang dapat kami tambahkan, seperti penanganan pengecualian, cache data, validasi data, atau apa pun, dan di seluruh proses desain dan pembuatan kami, hal paling berharga yang kami miliki yang memungkinkan kami menambahkan fitur sederhana tanpa mengubah kode yang ada. adalah IRepositoryabstraksi kami .

Sekarang, berkali-kali saya telah melihat pertanyaan ini di StackOverflow - "bagaimana Anda membuat Entity Framework berfungsi di lingkungan multi tenant?".

https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant

Jika Anda memiliki Repositoryabstraksi maka jawabannya adalah “mudah menambahkan dekorator”

public class RepositoryTennantFilterDecorator<T> : IRepository<T>
    where T : class
{
    //public for Unit Test example
    public readonly IRepository<T> _decorated;
    public RepositoryTennantFilterDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        return _decorated.Add(entity);
    }

    public void Delete(T entity)
    {
        _decorated.Delete(entity);
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable().Where(o => true);
    }
}

IMO Anda harus selalu menempatkan abstraksi sederhana di atas komponen pihak ketiga yang akan dirujuk di lebih dari beberapa tempat. Dari perspektif ini, ORM adalah kandidat yang tepat karena direferensikan di banyak kode kami.

Jawaban yang biasanya terlintas dalam pikiran ketika seseorang berkata "mengapa saya harus memiliki abstraksi (misalnya Repository) atas perpustakaan pihak ketiga ini atau itu" adalah "mengapa Anda tidak?"

Dekorator PS sangat mudah diterapkan menggunakan Kontainer IoC, seperti SimpleInjector .

[TestFixture]
public class IRepositoryTesting
{
    [Test]
    public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
    {
        Container container = new Container();
        container.RegisterLifetimeScope<PPContext>();
        container.RegisterOpenGeneric(
            typeof(IRepository<>), 
            typeof(Repository<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryLoggerDecorator<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryTennantFilterDecorator<>));
        container.Verify();

        using (container.BeginLifetimeScope())
        {
            var result = container.GetInstance<IRepository<Image>>();

            Assert.That(
                result, 
                Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
            Assert.That(
                (result as RepositoryTennantFilterDecorator<Image>)._decorated,
                Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
        }
    }
}
qujck
sumber
11

Pertama-tama, seperti yang disarankan oleh beberapa jawaban, EF sendiri adalah pola repositori, tidak perlu membuat abstraksi lebih lanjut hanya untuk menamainya sebagai repositori.

Repositori Mockable untuk Pengujian Unit, apakah kita benar-benar membutuhkannya?

Kami membiarkan EF berkomunikasi untuk menguji DB dalam tes unit untuk menguji logika bisnis kami langsung terhadap DB tes SQL. Saya sama sekali tidak melihat keuntungan dari meniru pola repositori apa pun. Apa yang salah melakukan pengujian unit terhadap database pengujian? Karena ini adalah operasi massal tidak dimungkinkan dan kami akhirnya menulis SQL mentah. SQLite dalam memori adalah kandidat yang tepat untuk melakukan pengujian unit terhadap database nyata.

Abstraksi yang Tidak Diperlukan

Apakah Anda ingin membuat repositori agar di masa mendatang Anda dapat dengan mudah mengganti EF dengan NHbibernate dll atau yang lainnya? Kedengarannya rencana yang bagus, tetapi apakah ini benar-benar hemat biaya?

Linq membunuh tes unit?

Saya ingin melihat contoh bagaimana itu bisa membunuh.

Injeksi Ketergantungan, IoC

Wow, ini kata-kata yang bagus, pasti terlihat bagus secara teori, tetapi terkadang Anda harus memilih pertukaran antara desain hebat dan solusi hebat. Kami memang menggunakan semua itu, dan akhirnya kami membuang semuanya dan memilih pendekatan yang berbeda. Ukuran vs Kecepatan (Ukuran kode dan Kecepatan pengembangan) sangat penting dalam kehidupan nyata. Pengguna membutuhkan fleksibilitas, mereka tidak peduli jika kode Anda bagus dalam desain dalam hal DI atau IoC.

Kecuali Anda sedang membangun Visual Studio

Semua desain hebat ini diperlukan jika Anda membangun program kompleks seperti Visual Studio atau Eclipse yang akan dikembangkan oleh banyak orang dan harus sangat dapat disesuaikan. Semua pola pengembangan yang hebat muncul setelah bertahun-tahun pengembangan yang dilakukan oleh IDE ini, dan mereka telah berkembang di tempat di mana semua pola desain yang hebat ini sangat berarti. Tetapi jika Anda melakukan penggajian berbasis web sederhana, atau aplikasi bisnis sederhana, lebih baik Anda mengembangkannya seiring waktu, daripada menghabiskan waktu untuk membangunnya untuk jutaan pengguna yang hanya akan digunakan untuk 100 pengguna.

Repositori sebagai Tampilan Tersaring - ISecureRepository

Di sisi lain, repositori harus berupa tampilan EF yang difilter yang menjaga akses ke data dengan menerapkan pengisi yang diperlukan berdasarkan pengguna / peran saat ini.

Tetapi melakukan hal itu semakin memperumit repositori karena berakhir dengan basis kode yang sangat besar untuk dipelihara. Orang-orang akhirnya membuat repositori yang berbeda untuk jenis pengguna yang berbeda atau kombinasi jenis entitas. Tidak hanya itu, kami juga memiliki banyak DTO.

Jawaban berikut adalah contoh implementasi Filtered Repository tanpa membuat seluruh rangkaian kelas dan metode. Ini mungkin tidak menjawab pertanyaan secara langsung tetapi dapat berguna dalam menurunkannya.

Penafian: Saya penulis Entity REST SDK.

http://entityrestsdk.codeplex.com

Dengan mengingat di atas, kami mengembangkan SDK yang membuat repositori tampilan yang difilter berdasarkan SecurityContext yang menampung filter untuk operasi CRUD. Dan hanya dua jenis aturan yang menyederhanakan operasi kompleks apa pun. Pertama adalah akses ke entitas, dan lainnya adalah aturan Baca / Tulis untuk properti.

Keuntungannya adalah, Anda tidak menulis ulang logika bisnis atau repositori untuk jenis pengguna yang berbeda, Anda cukup memblokir atau memberi mereka akses.

public class DefaultSecurityContext : BaseSecurityContext {

  public static DefaultSecurityContext Instance = new DefaultSecurityContext();

  // UserID for currently logged in User
  public static long UserID{
       get{
             return long.Parse( HttpContext.Current.User.Identity.Name );
       }
  }

  public DefaultSecurityContext(){
  }

  protected override void OnCreate(){

        // User can access his own Account only
        var acc = CreateRules<Account>();

        acc.SetRead( y => x=> x.AccountID == UserID ) ;
        acc.SetWrite( y => x=> x.AccountID == UserID );

        // User can only modify AccountName and EmailAddress fields
        acc.SetProperties( SecurityRules.ReadWrite, 
              x => x.AccountName,
              x => x.EmailAddress);

        // User can read AccountType field
        acc.SetProperties<Account>( SecurityRules.Read, 
              x => x.AccountType);

        // User can access his own Orders only
        var order = CreateRules<Order>();
        order.SetRead( y => x => x.CustomerID == UserID );

        // User can modify Order only if OrderStatus is not complete
        order.SetWrite( y => x => x.CustomerID == UserID 
            && x.OrderStatus != "Complete" );

        // User can only modify OrderNotes and OrderStatus
        order.SetProperties( SecurityRules.ReadWrite, 
              x => x.OrderNotes,
              x => x.OrderStatus );

        // User can not delete orders
        order.SetDelete(order.NotSupportedRule);
  }
}

Aturan LINQ ini dievaluasi terhadap Database dalam metode SaveChanges untuk setiap operasi, dan Aturan ini bertindak sebagai Firewall di depan Database.

Akash Kava
sumber
3
Pengujian unit terhadap DB berarti Anda memiliki persyaratan luar ekstra untuk pengujian Anda. Jika DB itu down, atau datanya dihapus, atau sesuatu terjadi pada DB itu, pengujian Anda akan gagal. Ini tidak diinginkan. Repositori yang mengekspos IQuer dapat membutuhkan waktu sekitar 2 menit untuk penyiapan. Tidak ada waktu yang terbuang di sini. Mengapa DI membutuhkan waktu lama? Semua ini membutuhkan waktu beberapa menit. Saya akan mengatakan ini semua berfungsi dengan baik untuk pengujian unit kueri kompleks saya di lapisan layanan saya. Sangat menyenangkan tidak membutuhkan database untuk terhubung. Mendapatkan kerangka kerja mengejek dari nuget membutuhkan waktu sekitar satu menit. Hal ini tidak memakan waktu lama.
pengguna441521
@ user441521 Repositori dengan IQuer dapat 2 menit untuk setup? di dunia mana Anda tinggal, setiap permintaan asp.net di situs langsung kami dilayani dalam milidetik. Mengolok-olok dan memalsukan dll menambah kerumitan kode, total pemborosan waktu. Tes unit tidak berguna ketika unit tidak didefinisikan sebagai unit logika bisnis.
Akash Kava
7

Ada banyak perdebatan tentang metode mana yang benar, jadi saya melihatnya karena keduanya dapat diterima jadi saya menggunakan yang mana yang paling saya sukai (Yang bukan repositori, UoW).

Dalam EF UoW diimplementasikan melalui DbContext dan DbSets adalah repositori.

Adapun cara bekerja dengan lapisan data saya hanya langsung mengerjakan objek DbContext, untuk kueri kompleks saya akan membuat metode ekstensi untuk kueri yang dapat digunakan kembali.

Saya yakin Ayende juga memiliki beberapa posting tentang bagaimana mengabstraksi operasi CUD itu buruk.

Saya selalu membuat antarmuka dan memiliki konteks yang diwarisi darinya sehingga saya dapat menggunakan wadah IoC untuk DI.

Josh
sumber
Jadi metode penyuluhannya, seberapa luaskah mereka? Katakanlah saya perlu mendapatkan status entitas lain di ekstensi saya? Itu adalah kekhawatiran terbesar saya saat ini. Anda keberatan menunjukkan beberapa contoh metode penyuluhan?
Dejan.S
ayende.com/blog/153473/… dan ayende.com/blog/153569/… . (Ini adalah review dari arsitektur (Framework?) Yang disebut s # arp lite. Sebagian besar bagus tapi dia tidak setuju dengan repositori dan abstraksi CUD).
Josh
Berbasis NHibernate. Apakah Anda tidak punya contoh menggunakan EF? Dan lagi ketika saya perlu memanggil entitas lain, bagaimana cara terbaiknya dalam metode ekstensi statis?
Dejan.S
3
Ini semua baik dan bagus sampai properti objek domain Anda perlu dihidrasi oleh data yang tidak disimpan di database Anda; atau Anda perlu beralih ke teknologi yang lebih berkinerja daripada ORM kembung Anda. OOPS! ORM BUKAN pengganti untuk repositori, ini adalah detail implementasi dari repositori.
cdaq
2

Apa yang paling diterapkan di EF bukanlah Pola Repositori. Ini adalah pola Fasad (mengabstraksi panggilan ke metode EF menjadi versi yang lebih sederhana dan lebih mudah digunakan).

EF adalah yang menerapkan Pola Repositori (dan pola Unit Kerja juga). Artinya, EF adalah yang mengabstraksi lapisan akses data sehingga pengguna tidak tahu bahwa mereka berurusan dengan SQLServer.

Dan pada saat itu, sebagian besar "repositori" di EF bahkan bukan Fasad yang bagus karena mereka hanya memetakan, secara langsung, ke metode tunggal di EF, bahkan sampai memiliki tanda tangan yang sama.

Dua alasan, kemudian, untuk menerapkan apa yang disebut pola "Repositori" di atas EF adalah untuk memungkinkan pengujian yang lebih mudah dan untuk membuat subset dari panggilan "terekam" untuk itu. Tidak buruk dalam dirinya sendiri, tetapi jelas bukan Repositori.

Aratirn
sumber
1

Linq adalah 'Tempat Penyimpanan' saat ini.

ISession + Linq sudah menjadi repositori, dan Anda tidak memerlukan GetXByYmetode maupunQueryData(Query q) generalisasi. Menjadi sedikit paranoid terhadap penggunaan DAL, saya masih lebih suka antarmuka repositori. (Dari sudut pandang pemeliharaan kita juga masih harus memiliki beberapa fasad di atas antarmuka akses data tertentu).

Ini adalah repositori yang kita gunakan - ia memisahkan kita dari penggunaan langsung nhibernate, tetapi menyediakan antarmuka linq (sebagai akses ISession dalam kasus luar biasa, yang pada akhirnya tunduk pada refactor).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}
mikalai
sumber
Apa yang Anda lakukan untuk lapisan layanan?
Dejan.S
Repo kueri pengontrol untuk data hanya-baca, mengapa menambahkan beberapa lapisan tambahan? Kemungkinan lainnya adalah menggunakan "ContentService" yang lebih cenderung menjadi repositori tingkat layanan: GetXByY, etc.etc. Untuk operasi modifikasi - layanan aplikasi hanyalah abstraksi dari kasus penggunaan - mereka menggunakan BL dan repo secara bebas ..
mikalai
Saya terbiasa melakukan lapisan layanan untuk logika bisnis. Saya tidak begitu yakin saya mengikuti apa yang Anda lakukan dengan ContentService, harap jelaskan. Apakah praktik yang buruk untuk melakukan kelas pembantu sebagai "lapisan layanan"?
Dejan.S
Yang saya maksud dengan 'lapisan layanan' adalah 'layanan aplikasi'. Mereka dapat menggunakan repositori dan bagian publik lainnya dari lapisan domain. "Lapisan layanan" bukanlah praktik yang buruk, tetapi saya akan menghindari membuat kelas XService hanya untuk memberikan hasil Daftar <X>. Bidang komentar tampaknya terlalu pendek untuk menggambarkan layanan secara detail, maaf.
mikalai
Bagaimana jika, katakanlah perhitungan keranjang dan Anda perlu mendapatkan parameter pengaturan aplikasi dan parameter pelanggan tertentu untuk membuat perhitungan, dan ini digunakan kembali di beberapa tempat dalam aplikasi. Bagaimana Anda menangani situasi itu? kelas pembantu atau layanan aplikasi?
Dejan.S
1

The Repository (atau namun satu lagi memilih untuk menyebutnya) saat ini bagi saya adalah kebanyakan tentang abstrak pergi lapisan ketekunan.

Saya menggunakannya digabungkan dengan objek kueri jadi saya tidak memiliki kopling ke teknologi tertentu dalam aplikasi saya. Dan juga sangat memudahkan pengujian.

Jadi, saya cenderung punya

public interface IRepository : IDisposable
{
    void Save<TEntity>(TEntity entity);
    void SaveList<TEntity>(IEnumerable<TEntity> entities);

    void Delete<TEntity>(TEntity entity);
    void DeleteList<TEntity>(IEnumerable<TEntity> entities);

    IList<TEntity> GetAll<TEntity>() where TEntity : class;
    int GetCount<TEntity>() where TEntity : class;

    void StartConversation();
    void EndConversation();

    //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

Mungkin menambahkan metode asinkron dengan callback sebagai delegasi. Repo ini mudah diimplementasikan secara umum , jadi saya tidak bisa menyentuh garis implementasi dari aplikasi ke aplikasi. Ya, ini benar setidaknya saat menggunakan NH, saya juga melakukannya dengan EF, tapi membuat saya membenci EF. 4. Percakapan adalah awal dari sebuah transaksi. Sangat keren jika beberapa kelas berbagi instance repositori. Juga, untuk NH, satu repo dalam implementasi saya sama dengan satu sesi yang dibuka pada permintaan pertama.

Kemudian Objek Query

public interface IQueryObject<TResult>
{
    /// <summary>Provides configuration options.</summary>
    /// <remarks>
    /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
    /// If not used through a repository, it can be useful as a configuration option.
    /// </remarks>
    void Configure(object parameter);

    /// <summary>Implementation of the query.</summary>
    TResult GetResult();
}

Untuk konfigurasi yang saya gunakan di NH hanya untuk lulus di ISession. Di EF tidak masuk akal lebih atau kurang.

Contoh kueri akan menjadi .. (NH)

public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
    where TEntity : class
{
    public override IList<TEntity> GetResult()
    {
        return this.Session.CreateCriteria<TEntity>().List<TEntity>();
    }
}

Untuk melakukan kueri EF Anda harus memiliki konteks dalam basis Abstrak, bukan sesi. Tapi tentu saja ifc akan sama.

Dengan cara ini, kueri itu sendiri dikemas, dan mudah diuji. Yang terbaik dari semuanya, kode saya hanya bergantung pada antarmuka. Semuanya sangat bersih. Objek domain (bisnis) hanyalah itu, misalnya tidak ada pencampuran tanggung jawab seperti saat menggunakan pola rekaman aktif yang hampir tidak dapat diuji dan mencampur kode akses data (kueri) dalam objek domain dan dalam melakukan itu mencampur masalah (objek yang mengambil diri??). Semua orang masih bebas membuat POCO untuk transfer data.

Secara keseluruhan, banyak penggunaan kembali kode dan kesederhanaan disediakan dengan pendekatan ini tanpa kehilangan apa pun yang dapat saya bayangkan. Ada ide?

Dan terima kasih banyak kepada Ayende untuk postingannya yang bagus dan dedikasinya yang berkelanjutan. Ini adalah idenya di sini (objek kueri), bukan milik saya.

h. alex
sumber
1
Entitas persistensi (POCO Anda) BUKAN entitas bisnis / domain. Dan tujuan repositori adalah untuk memisahkan lapisan bisnis (semua) dari persistensi.
MikeSW
Saya gagal melihat koplingnya. Agak setuju dengan bagian POCO, tapi tidak peduli. Tidak ada yang menghentikan Anda untuk memiliki POCO 'asli' dan tetap menggunakan pendekatan ini.
h. Alex
1
Entitas tidak harus menjadi POCO bodoh sama sekali. Faktanya, memodelkan logika bisnis ke dalam Entitas adalah apa yang dilakukan kerumunan DDD sepanjang waktu. Gaya pengembangan ini sangat cocok dengan NH atau EF.
Chris
1

Bagi saya, ini keputusan sederhana, dengan faktor yang relatif sedikit. Faktor-faktor tersebut adalah:

  1. Repositori untuk kelas domain.
  2. Di beberapa aplikasi saya, kelas domain sama dengan kelas ketekunan (DAL) saya, di aplikasi lain tidak.
  3. Jika mereka sama, EF sudah menyediakan Repositori untuk saya.
  4. EF menyediakan pemuatan lambat dan IQuerizable. Aku suka ini.
  5. Abstrak / 'facading' / repositori yang diimplementasikan ulang melalui EF biasanya berarti hilangnya malas dan IQuerizable

Jadi, jika aplikasi saya tidak dapat membenarkan # 2, domain terpisah dan model data, maka saya biasanya tidak akan peduli dengan # 5.

Pucat
sumber