Perintah dan / atau spesifikasi kueri yang dirancang dengan baik

92

Saya telah mencari cukup lama untuk solusi yang baik untuk masalah yang disajikan oleh pola Repositori khas (daftar metode yang berkembang untuk kueri khusus, dll .. lihat: http://ayende.com/blog/3955/repository- is-the-new-singleton ).

Saya sangat menyukai gagasan menggunakan kueri Perintah, terutama melalui penggunaan pola Spesifikasi. Namun, masalah saya dengan spesifikasi adalah bahwa ini hanya berkaitan dengan kriteria pilihan sederhana (pada dasarnya, klausa where), dan tidak berurusan dengan masalah kueri lainnya, seperti bergabung, pengelompokan, pemilihan subset atau proyeksi, dll. pada dasarnya, semua rintangan ekstra yang harus dilalui banyak kueri untuk mendapatkan kumpulan data yang benar.

(catatan: Saya menggunakan istilah "perintah" seperti dalam pola Perintah, juga dikenal sebagai objek kueri. Saya tidak berbicara tentang perintah seperti dalam pemisahan perintah / kueri di mana ada perbedaan yang dibuat antara kueri dan perintah (perbarui, hapus, memasukkan))

Jadi saya mencari alternatif yang merangkum seluruh kueri, tetapi masih cukup fleksibel sehingga Anda tidak hanya menukar spaghetti Repositories untuk ledakan kelas perintah.

Saya telah menggunakan, misalnya Linqspecs, dan sementara saya menemukan beberapa nilai dalam dapat menetapkan nama yang bermakna ke kriteria pemilihan, itu tidak cukup. Mungkin saya sedang mencari solusi campuran yang menggabungkan berbagai pendekatan.

Saya mencari solusi yang mungkin telah dikembangkan orang lain untuk mengatasi masalah ini, atau mengatasi masalah yang berbeda tetapi masih memenuhi persyaratan ini. Dalam artikel yang ditautkan, Ayende menyarankan untuk menggunakan konteks nHibernate secara langsung, tetapi saya merasa itu sebagian besar memperumit lapisan bisnis Anda karena sekarang juga harus berisi informasi kueri.

Saya akan menawarkan hadiah untuk ini, segera setelah masa tunggu berlalu. Jadi tolong buat solusi Anda layak, dengan penjelasan yang baik dan saya akan memilih solusi terbaik, dan memberi suara positif kepada runner up.

CATATAN: Saya mencari sesuatu yang berbasis ORM. Tidak harus EF atau nHibernate secara eksplisit, tetapi itu adalah yang paling umum dan paling cocok. Jika dapat dengan mudah diadaptasi ke ORM lain itu akan menjadi bonus. Kompatibel dengan Linq juga akan menyenangkan.

PEMBARUAN: Saya sangat terkejut bahwa tidak banyak saran bagus di sini. Sepertinya orang-orang benar-benar CQRS, atau mereka sepenuhnya berada di kamp Repositori. Sebagian besar aplikasi saya tidak cukup rumit untuk menjamin CQRS (sesuatu dengan sebagian besar pendukung CQRS dengan mudah mengatakan bahwa Anda tidak boleh menggunakannya untuk).

UPDATE: Sepertinya ada sedikit kebingungan di sini. Saya tidak mencari teknologi akses data baru, melainkan antarmuka yang dirancang dengan cukup baik antara bisnis dan data.

Idealnya, yang saya cari adalah semacam persilangan antara objek Query, pola spesifikasi, dan repositori. Seperti yang saya katakan di atas, pola spesifikasi hanya berurusan dengan aspek klausa di mana, dan bukan aspek lain dari kueri, seperti gabungan, sub-pilihan, dll. Repositori menangani seluruh kueri, tetapi lepas kendali setelah beberapa saat . Objek kueri juga menangani seluruh kueri, tetapi saya tidak ingin hanya mengganti repositori dengan ledakan objek kueri.

Erik Funkenbusch
sumber
5
Pertanyaan yang fantastis. Saya juga ingin melihat apa yang orang-orang dengan pengalaman lebih dari yang saya sarankan. Saya sedang mengerjakan basis kode saat ini di mana repositori generik juga berisi kelebihan untuk objek Command atau objek Query, yang strukturnya mirip dengan apa yang dijelaskan Ayende di blognya. PS: Ini mungkin juga menarik perhatian programmer.SE.
Simon Whitehead
Mengapa tidak menggunakan repositori yang mengekspos IQuerizable jika Anda tidak keberatan dengan ketergantungan pada LINQ? Pendekatan umum adalah repositori generik, dan saat Anda membutuhkan logika yang dapat digunakan kembali di atas, Anda membuat tipe repositori turunan dengan metode tambahan Anda.
devdigital
@devdigital - Ketergantungan pada LINQ tidak sama dengan ketergantungan pada implementasi data. Saya ingin menggunakan Linq ke objek, jadi saya bisa mengurutkan atau menjalankan fungsi lapisan bisnis lainnya. Tetapi itu tidak berarti saya menginginkan ketergantungan pada implementasi model data. Apa yang sebenarnya saya bicarakan di sini adalah antarmuka lapisan / tingkat. Sebagai contoh, saya ingin dapat mengubah kueri dan tidak harus mengubahnya di 200 tempat, itulah yang terjadi jika Anda mendorong IQuerizable langsung ke dalam model bisnis.
Erik Funkenbusch
1
@devdigital - yang pada dasarnya hanya memindahkan masalah dengan repositori ke lapisan bisnis Anda. Anda hanya mengacak-acak masalahnya.
Erik Funkenbusch

Jawaban:

95

Penafian: Karena belum ada jawaban yang bagus, saya memutuskan untuk memposting bagian dari posting blog hebat yang saya baca beberapa waktu yang lalu, disalin hampir kata demi kata. Anda dapat menemukan postingan blog lengkapnya di sini . Jadi begini:


Kita dapat mendefinisikan dua antarmuka berikut:

public interface IQuery<TResult>
{
}

public interface IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
{
    TResult Handle(TQuery query);
}

The IQuery<TResult>Menentukan pesan yang mendefinisikan query tertentu dengan data itu kembali menggunakan TResultjenis generik. Dengan antarmuka yang ditentukan sebelumnya, kita dapat mendefinisikan pesan kueri seperti ini:

public class FindUsersBySearchTextQuery : IQuery<User[]>
{
    public string SearchText { get; set; }
    public bool IncludeInactiveUsers { get; set; }
}

Kelas ini mendefinisikan operasi kueri dengan dua parameter, yang akan menghasilkan larik Userobjek. Kelas yang menangani pesan ini dapat didefinisikan sebagai berikut:

public class FindUsersBySearchTextQueryHandler
    : IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
    private readonly NorthwindUnitOfWork db;

    public FindUsersBySearchTextQueryHandler(NorthwindUnitOfWork db)
    {
        this.db = db;
    }

    public User[] Handle(FindUsersBySearchTextQuery query)
    {
        return db.Users.Where(x => x.Name.Contains(query.SearchText)).ToArray();
    }
}

Sekarang kita dapat membiarkan konsumen bergantung pada IQueryHandlerantarmuka generik :

public class UserController : Controller
{
    IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler;

    public UserController(
        IQueryHandler<FindUsersBySearchTextQuery, User[]> findUsersBySearchTextHandler)
    {
        this.findUsersBySearchTextHandler = findUsersBySearchTextHandler;
    }

    public View SearchUsers(string searchString)
    {
        var query = new FindUsersBySearchTextQuery
        {
            SearchText = searchString,
            IncludeInactiveUsers = false
        };

        User[] users = this.findUsersBySearchTextHandler.Handle(query);    
        return View(users);
    }
}

Model ini segera memberi kami banyak fleksibilitas, karena sekarang kami dapat memutuskan apa yang akan dimasukkan ke dalam UserController. Kami dapat memasukkan implementasi yang sama sekali berbeda, atau implementasi yang membungkus implementasi nyata, tanpa harus melakukan perubahan pada UserController(dan semua konsumen lain dari antarmuka tersebut).

The IQuery<TResult>antarmuka memberi kita waktu kompilasi dukungan ketika menentukan atau suntik IQueryHandlersdalam kode kita. Ketika kita mengubah FindUsersBySearchTextQuerykembali UserInfo[]bukan (dengan menerapkan IQuery<UserInfo[]>), yang UserControllerakan gagal dikompilasi, karena jenis kendala generik pada IQueryHandler<TQuery, TResult>tidak akan mampu memetakan FindUsersBySearchTextQueryke User[].

Menyuntikkan IQueryHandlerantarmuka ke konsumen Namun, memiliki beberapa masalah kurang jelas yang masih perlu dibenahi. Jumlah ketergantungan konsumen kita mungkin menjadi terlalu besar dan dapat menyebabkan konstruktor kelebihan injeksi - ketika konstruktor mengambil terlalu banyak argumen. Jumlah kueri yang dijalankan kelas bisa sering berubah, yang akan membutuhkan perubahan konstan ke dalam jumlah argumen konstruktor.

Kami dapat memperbaiki masalah karena harus menyuntikkan terlalu banyak IQueryHandlersdengan lapisan abstraksi ekstra. Kami membuat mediator yang berada di antara konsumen dan penangan kueri:

public interface IQueryProcessor
{
    TResult Process<TResult>(IQuery<TResult> query);
}

Ini IQueryProcessoradalah antarmuka non-generik dengan satu metode umum. Seperti yang Anda lihat dalam definisi antarmuka, IQueryProcessortergantung pada IQuery<TResult>antarmuka. Hal ini memungkinkan kami untuk memiliki dukungan waktu kompilasi pada konsumen kami yang bergantung pada IQueryProcessor. Mari tulis ulang UserControlleruntuk menggunakan yang baru IQueryProcessor:

public class UserController : Controller
{
    private IQueryProcessor queryProcessor;

    public UserController(IQueryProcessor queryProcessor)
    {
        this.queryProcessor = queryProcessor;
    }

    public View SearchUsers(string searchString)
    {
        var query = new FindUsersBySearchTextQuery
        {
            SearchText = searchString,
            IncludeInactiveUsers = false
        };

        // Note how we omit the generic type argument,
        // but still have type safety.
        User[] users = this.queryProcessor.Process(query);

        return this.View(users);
    }
}

The UserControllersekarang tergantung pada IQueryProcessoryang dapat menangani semua pertanyaan kita. The UserController's SearchUsersmetode memanggil IQueryProcessor.Processmetode yang lewat di sebuah objek query diinisialisasi. Karena FindUsersBySearchTextQuerymengimplementasikan IQuery<User[]>antarmuka, kita dapat meneruskannya ke Execute<TResult>(IQuery<TResult> query)metode umum . Berkat inferensi tipe C #, kompilator dapat menentukan tipe generik dan ini membuat kita tidak perlu menyatakan tipe secara eksplisit. Jenis kembalian dari Processmetode ini juga dikenal.

Sekarang menjadi tanggung jawab implementasi IQueryProcessoruntuk menemukan hak IQueryHandler. Ini memerlukan beberapa pengetikan dinamis, dan secara opsional menggunakan kerangka kerja Dependency Injection, dan semuanya dapat dilakukan hanya dengan beberapa baris kode:

sealed class QueryProcessor : IQueryProcessor
{
    private readonly Container container;

    public QueryProcessor(Container container)
    {
        this.container = container;
    }

    [DebuggerStepThrough]
    public TResult Process<TResult>(IQuery<TResult> query)
    {
        var handlerType = typeof(IQueryHandler<,>)
            .MakeGenericType(query.GetType(), typeof(TResult));

        dynamic handler = container.GetInstance(handlerType);

        return handler.Handle((dynamic)query);
    }
}

The QueryProcessorkelas membangun sebuah tertentu IQueryHandler<TQuery, TResult>jenis berdasarkan pada jenis contoh permintaan disediakan. Tipe ini digunakan untuk meminta kelas kontainer yang disediakan untuk mendapatkan sebuah instance dari tipe itu. Sayangnya kita perlu memanggil Handlemetode menggunakan refleksi (dengan menggunakan kata kunci dymamic C # 4.0 dalam kasus ini), karena pada titik ini tidak mungkin untuk mentransmisikan contoh handler, karena TQueryargumen generik tidak tersedia pada waktu kompilasi. Namun, kecuali Handlemetode tersebut diubah namanya atau mendapat argumen lain, panggilan ini tidak akan pernah gagal dan jika Anda mau, sangat mudah untuk menulis pengujian unit untuk kelas ini. Menggunakan refleksi akan memberikan sedikit penurunan, tetapi tidak ada yang perlu dikhawatirkan.


Untuk menjawab salah satu kekhawatiran Anda:

Jadi saya mencari alternatif yang merangkum seluruh kueri, tetapi masih cukup fleksibel sehingga Anda tidak hanya menukar spaghetti Repositories untuk ledakan kelas perintah.

Konsekuensi dari penggunaan desain ini adalah akan ada banyak kelas kecil dalam sistem, tetapi memiliki banyak kelas kecil / terfokus (dengan nama yang jelas) adalah hal yang baik. Pendekatan ini jelas jauh lebih baik daripada memiliki banyak kelebihan beban dengan parameter berbeda untuk metode yang sama dalam repositori, karena Anda dapat mengelompokkannya dalam satu kelas kueri. Jadi, Anda masih mendapatkan kelas kueri yang jauh lebih sedikit daripada metode dalam repositori.

david.s
sumber
2
Sepertinya Anda mendapatkan penghargaannya. Saya menyukai konsepnya, saya hanya berharap seseorang menyajikan sesuatu yang benar-benar berbeda. Selamat.
Erik Funkenbusch
1
@ FuriCuri, apakah satu kelas benar-benar membutuhkan 5 kueri? Mungkin Anda bisa melihatnya sebagai kelas dengan terlalu banyak tanggung jawab. Alternatifnya, jika kueri digabungkan maka mungkin kueri tersebut sebenarnya harus menjadi satu kueri. Ini hanyalah saran, tentunya.
Sam
1
@stakx Anda benar sekali bahwa dalam contoh awal saya, TResultparameter umum IQueryantarmuka tidak berguna. Namun, dalam tanggapan saya yang diperbarui, TResultparameter digunakan oleh Processmetode IQueryProcessoruntuk menyelesaikan pada IQueryHandlersaat runtime.
david.
1
Saya juga memiliki blog dengan implementasi yang sangat mirip yang membuat saya berpikir bahwa saya berada di jalur yang benar, ini adalah tautan jupaol.blogspot.mx/2012/11/… dan saya telah menggunakannya beberapa lama di aplikasi PROD, tapi saya punya masalah dengan pendekatan ini. Merangkai dan menggunakan kembali kueri Katakanlah saya memiliki beberapa kueri kecil yang perlu digabungkan untuk membuat kueri yang lebih kompleks, saya akhirnya hanya menggandakan kode tetapi saya mencari pendekatan moch yang lebih baik dan lebih bersih. Ada ide?
Jupaol
4
@Cemre Saya akhirnya merangkum pertanyaan saya dalam metode Ekstensi kembali IQueryabledan memastikan untuk tidak menghitung koleksi, lalu dari QueryHandlersaya baru saja memanggil / merangkai kueri. Ini memberi saya fleksibilitas untuk menguji kueri saya dan merangkainya. Saya memiliki layanan aplikasi di atas saya QueryHandler, dan pengontrol saya bertanggung jawab untuk berbicara langsung dengan layanan alih-alih penangan
Jupaol
4

Cara saya mengatasinya sebenarnya sederhana dan ORM agnostik. Pandangan saya untuk repositori adalah ini: Tugas repositori adalah menyediakan aplikasi dengan model yang diperlukan untuk konteksnya, jadi aplikasi hanya menanyakan repo untuk apa yang diinginkannya tetapi tidak memberi tahu cara mendapatkannya.

Saya menyediakan metode repositori dengan Kriteria (ya, gaya DDD), yang akan digunakan oleh repo untuk membuat kueri (atau apa pun yang diperlukan - ini mungkin permintaan layanan web). Gabungan dan kelompok imho adalah rincian bagaimana, bukan apa dan kriteria seharusnya hanya menjadi dasar untuk membangun klausa where.

Model = objek akhir atau struktur data yang dibutuhkan oleh aplikasi.

public class MyCriteria
{
   public Guid Id {get;set;}
   public string Name {get;set;}
    //etc
 }

 public interface Repository
  {
       MyModel GetModel(Expression<Func<MyCriteria,bool>> criteria);
   }

Mungkin Anda dapat menggunakan kriteria ORM (Nhibernate) secara langsung jika Anda menginginkannya. Implementasi repositori harus mengetahui cara menggunakan Kriteria dengan penyimpanan yang mendasari atau DAO.

Saya tidak tahu domain Anda dan persyaratan modelnya, tetapi akan aneh jika cara terbaik adalah aplikasi tersebut membuat kueri itu sendiri. Modelnya berubah begitu banyak sehingga Anda tidak dapat mendefinisikan sesuatu yang stabil?

Solusi ini jelas memerlukan beberapa kode tambahan tetapi tidak memasangkan sisanya ke ORM atau apa pun yang Anda gunakan untuk mengakses penyimpanan. Repositori melakukan tugasnya untuk bertindak sebagai fasad dan IMO bersih dan kode 'terjemahan kriteria' dapat digunakan kembali

MikeSW
sumber
Ini tidak mengatasi masalah pertumbuhan repositori, dan memiliki daftar metode yang terus bertambah untuk mengembalikan berbagai jenis data. Saya mengerti Anda mungkin tidak melihat masalah dengan ini (banyak orang tidak), tetapi orang lain melihatnya secara berbeda (saya sarankan membaca artikel yang saya tautkan, ada banyak orang lain dengan pendapat serupa).
Erik Funkenbusch
1
Saya mengatasinya, karena kriterianya membuat banyak metode tidak diperlukan. Tentu saja, tidak semuanya saya tidak bisa bicara banyak tanpa mengetahui apa-apa tentang ting yang Anda butuhkan. Saya sedang terkesan bahwa Anda ingin menanyakan secara langsung db jadi, mungkin repositori ada di jalan. Jika Anda perlu bekerja langsung dengan sotrage relasional, lakukan langsung, tidak perlu repositori. Dan sebagai catatan, betapa menjengkelkan banyaknya orang yang mengutip Ayende dengan postingan itu. Saya tidak setuju dengan itu dan saya pikir banyak pengembang hanya menggunakan pola dengan cara yang salah.
MikeSW
1
Ini mungkin sedikit mengurangi masalah, tetapi mengingat aplikasi yang cukup besar itu masih akan membuat repositori monster. Saya tidak setuju dengan solusi Ayende yang menggunakan nHibernate secara langsung dalam logika utama, tetapi saya setuju dengannya tentang absurditas pertumbuhan repositori di luar kendali. Saya tidak ingin menanyakan database secara langsung, tetapi saya tidak hanya ingin memindahkan masalah dari repositori ke ledakan objek kueri juga.
Erik Funkenbusch
2

Saya telah melakukan ini, mendukung ini dan membatalkan ini.

Masalah utamanya adalah ini: tidak peduli bagaimana Anda melakukannya, abstraksi yang ditambahkan tidak membuat Anda mandiri. Ini akan bocor menurut definisi. Intinya, Anda menciptakan seluruh lapisan hanya untuk membuat kode Anda terlihat lucu ... tetapi tidak mengurangi pemeliharaan, meningkatkan keterbacaan, atau memberi Anda semua jenis model agnostisisme.

Bagian yang menyenangkan adalah Anda menjawab pertanyaan Anda sendiri sebagai tanggapan atas tanggapan Olivier: "ini pada dasarnya menduplikasi fungsionalitas LINQ tanpa semua manfaat yang Anda peroleh dari LINQ".

Tanyakan pada diri Anda: bagaimana mungkin tidak?

Stu
sumber
Yah, saya pasti pernah mengalami masalah dalam mengintegrasikan Linq ke dalam lapisan bisnis Anda. Ini sangat kuat, tetapi ketika kami membuat perubahan model data, itu adalah mimpi buruk. Hal-hal diperbaiki dengan repositori, karena saya dapat membuat perubahan di tempat yang dilokalkan tanpa mempengaruhi banyak lapisan bisnis (selain ketika Anda juga harus mengubah lapisan bisnis untuk mendukung perubahan). Tapi, repositori menjadi lapisan yang membengkak yang melanggar SRP secara masif. Saya mengerti maksud Anda, tetapi itu juga tidak benar-benar menyelesaikan masalah apa pun.
Erik Funkenbusch
Jika lapisan data Anda menggunakan LINQ, dan perubahan model data memerlukan perubahan pada lapisan bisnis Anda ... Anda tidak membuat lapisan dengan benar.
Stu
Saya pikir Anda mengatakan Anda tidak lagi menambahkan lapisan itu. Ketika Anda mengatakan abstraksi yang ditambahkan tidak memberi Anda apa-apa, itu menyiratkan bahwa Anda setuju dengan Ayende tentang meneruskan sesi nHibernate (atau konteks EF) langsung ke lapisan bisnis Anda.
Erik Funkenbusch
1

Anda dapat menggunakan antarmuka yang lancar. Ide dasarnya adalah bahwa metode kelas mengembalikan instance saat ini kelas ini setelah melakukan beberapa tindakan. Ini memungkinkan Anda untuk menyambung panggilan metode.

Dengan membuat hierarki kelas yang sesuai, Anda dapat membuat alur logis dari metode yang dapat diakses.

public class FinalQuery
{
    protected string _table;
    protected string[] _selectFields;
    protected string _where;
    protected string[] _groupBy;
    protected string _having;
    protected string[] _orderByDescending;
    protected string[] _orderBy;

    protected FinalQuery()
    {
    }

    public override string ToString()
    {
        var sb = new StringBuilder("SELECT ");
        AppendFields(sb, _selectFields);
        sb.AppendLine();

        sb.Append("FROM ");
        sb.Append("[").Append(_table).AppendLine("]");

        if (_where != null) {
            sb.Append("WHERE").AppendLine(_where);
        }

        if (_groupBy != null) {
            sb.Append("GROUP BY ");
            AppendFields(sb, _groupBy);
            sb.AppendLine();
        }

        if (_having != null) {
            sb.Append("HAVING").AppendLine(_having);
        }

        if (_orderBy != null) {
            sb.Append("ORDER BY ");
            AppendFields(sb, _orderBy);
            sb.AppendLine();
        } else if (_orderByDescending != null) {
            sb.Append("ORDER BY ");
            AppendFields(sb, _orderByDescending);
            sb.Append(" DESC").AppendLine();
        }

        return sb.ToString();
    }

    private static void AppendFields(StringBuilder sb, string[] fields)
    {
        foreach (string field in fields) {
            sb.Append(field).Append(", ");
        }
        sb.Length -= 2;
    }
}

public class GroupedQuery : FinalQuery
{
    protected GroupedQuery()
    {
    }

    public GroupedQuery Having(string condition)
    {
        if (_groupBy == null) {
            throw new InvalidOperationException("HAVING clause without GROUP BY clause");
        }
        if (_having == null) {
            _having = " (" + condition + ")";
        } else {
            _having += " AND (" + condition + ")";
        }
        return this;
    }

    public FinalQuery OrderBy(params string[] fields)
    {
        _orderBy = fields;
        return this;
    }

    public FinalQuery OrderByDescending(params string[] fields)
    {
        _orderByDescending = fields;
        return this;
    }
}

public class Query : GroupedQuery
{
    public Query(string table, params string[] selectFields)
    {
        _table = table;
        _selectFields = selectFields;
    }

    public Query Where(string condition)
    {
        if (_where == null) {
            _where = " (" + condition + ")";
        } else {
            _where += " AND (" + condition + ")";
        }
        return this;
    }

    public GroupedQuery GroupBy(params string[] fields)
    {
        _groupBy = fields;
        return this;
    }
}

Anda akan menyebutnya seperti ini

string query = new Query("myTable", "name", "SUM(amount) AS total")
    .Where("name LIKE 'A%'")
    .GroupBy("name")
    .Having("COUNT(*) > 2")
    .OrderBy("name")
    .ToString();

Anda hanya dapat membuat contoh baru dari Query. Kelas-kelas lain memiliki konstruktor yang dilindungi. Inti dari hierarki ini adalah untuk "menonaktifkan" metode. Misalnya, fileGroupBy metode tersebut mengembalikan GroupedQueryyang merupakan kelas dasar Querydan tidak memiliki Wheremetode (metode tempat dideklarasikan Query). Oleh karena itu tidak mungkin untuk menelepon Wheresetelahnya GroupBy.

Namun itu tidak sempurna. Dengan hierarki kelas ini, Anda dapat menyembunyikan anggota secara berturut-turut, tetapi tidak menampilkan yang baru. Oleh karena itu Havingmelontarkan pengecualian saat dipanggil sebelumnya GroupBy.

Perhatikan bahwa panggilan dapat dilakukan Wherebeberapa kali. Ini menambahkan kondisi baru dengan kondisi ANDyang ada. Hal ini mempermudah pembuatan filter secara terprogram dari satu kondisi. Hal yang sama dimungkinkan dengan Having.

Metode yang menerima daftar bidang memiliki parameter params string[] fields. Ini memungkinkan Anda untuk melewatkan nama bidang tunggal atau larik string.


Antarmuka yang lancar sangat fleksibel dan tidak mengharuskan Anda membuat banyak metode berlebih dengan kombinasi parameter yang berbeda. Contoh saya bekerja dengan string, namun pendekatannya dapat diperluas ke jenis lain. Anda juga bisa mendeklarasikan metode yang telah ditentukan untuk kasus khusus atau metode yang menerima tipe kustom. Anda juga bisa menambahkan metode seperti ExecuteReaderatau ExceuteScalar<T>. Ini akan memungkinkan Anda untuk menentukan kueri seperti ini

var reader = new Query<Employee>(new MonthlyReportFields{ IncludeSalary = true })
    .Where(new CurrentMonthCondition())
    .Where(new DivisionCondition{ DivisionType = DivisionType.Production})
    .OrderBy(new StandardMonthlyReportSorting())
    .ExecuteReader();

Bahkan perintah SQL yang dibangun dengan cara ini dapat memiliki parameter perintah dan dengan demikian menghindari masalah injeksi SQL dan pada saat yang sama memungkinkan perintah untuk di-cache oleh server database. Ini bukan pengganti untuk O / R-mapper tetapi dapat membantu dalam situasi di mana Anda akan membuat perintah menggunakan penggabungan string sederhana sebaliknya.

Olivier Jacot-Descombes
sumber
3
Hmm .. Menarik, tetapi solusi Anda tampaknya memiliki masalah dengan kemungkinan SQL Injection, dan tidak benar-benar membuat pernyataan yang disiapkan untuk eksekusi yang telah dikompilasi sebelumnya (sehingga bekerja lebih lambat). Ini mungkin bisa diadaptasi untuk memperbaiki masalah tersebut, tapi kemudian kami terjebak dengan hasil dataset non-type safe dan yang tidak. Saya lebih suka solusi berbasis ORM, dan mungkin saya harus menentukannya secara eksplisit. Ini pada dasarnya menduplikasi fungsionalitas LINQ tanpa semua manfaat yang Anda peroleh dari LINQ.
Erik Funkenbusch
Saya menyadari masalah ini. Ini hanyalah solusi cepat dan kotor, yang menunjukkan bagaimana antarmuka yang lancar dapat dibangun. Dalam solusi dunia nyata, Anda mungkin akan "memanggang" pendekatan yang ada menjadi antarmuka yang fasih yang disesuaikan dengan kebutuhan Anda.
Olivier Jacot-Descombes