İyi tasarlanmış sorgu komutları ve / veya spesifikasyonlar


92

Tipik Depo modeli tarafından sunulan sorunlara iyi bir çözüm bulmak için epey zamandır arıyordum (özel sorgular için artan yöntemler listesi, vb. Bkz: http://ayende.com/blog/3955/repository- is-the-new-singleton ).

Komut sorgularını, özellikle Spesifikasyon modelini kullanarak kullanma fikrini gerçekten seviyorum. Bununla birlikte, spesifikasyonla ilgili sorunum, yalnızca basit seçim kriterleriyle (temelde, nerede cümlesi) ilgili olması ve birleştirme, gruplama, alt küme seçimi veya projeksiyon vb. Gibi diğer sorgu sorunlarıyla ilgilenmemesidir. temel olarak, doğru veri kümesini elde etmek için birçok sorgunun geçmesi gereken tüm ekstra çemberler.

(not: Sorgu nesneleri olarak da bilinen Komut deseninde olduğu gibi "komut" terimini kullanıyorum. Sorgular ve komutlar arasında bir ayrımın yapıldığı komut / sorgu ayırmada olduğu gibi komuttan bahsetmiyorum (güncelleme, silme, ekle))

Bu yüzden, tüm sorguyu kapsayan alternatifler arıyorum, ancak yine de yeterince esnek bir komut sınıfları patlaması için spagetti Depolarını değiştirmeyeceksiniz.

Örneğin Linqspecs'i kullandım ve seçim kriterlerine anlamlı isimler atayabilmek için bir miktar değer bulsam da, bu yeterli değil. Belki birden çok yaklaşımı birleştiren harmanlanmış bir çözüm arıyorum.

Başkalarının bu sorunu çözmek veya farklı bir sorunu ele almak için geliştirmiş olabileceği ancak yine de bu gereksinimleri karşılayan çözümler arıyorum. Bağlantılı makalede Ayende, nHibernate bağlamını doğrudan kullanmayı öneriyor, ancak bunun iş katmanınızı büyük ölçüde karmaşıklaştırdığını düşünüyorum çünkü artık sorgu bilgilerini de içermesi gerekiyor.

Bekleme süresi biter bitmez bu konuda bir ödül teklif edeceğim. Bu yüzden lütfen çözümlerinizi iyi açıklamalarla ödüllendirmeye değer yapın, ben de en iyi çözümü seçip ikincilere olumlu oy vereceğim.

NOT: ORM tabanlı bir şey arıyorum. Açıkça EF veya nHibernate olması gerekmez, ancak bunlar en yaygın olanıdır ve en iyisine uyacaktır. Diğer ORM'lere kolayca adapte edilebilirse bu bir bonus olur. Linq uyumlu da güzel olurdu.

GÜNCELLEME: Burada pek çok iyi öneri olmamasına gerçekten şaşırdım. Görünüşe göre insanlar ya tamamen CQRS ya da tamamen Depo kampındalar. Uygulamalarımın çoğu, CQRS'yi garanti edecek kadar karmaşık değil (çoğu CQRS savunucusu, onu bunun için kullanmaman gerektiğini söylüyor).

GÜNCELLEME: Burada biraz karışıklık var gibi görünüyor. Yeni bir veri erişim teknolojisi arıyorum, bunun yerine iş ve veri arasında oldukça iyi tasarlanmış bir arayüz arıyorum.

İdeal olarak, aradığım şey Sorgu nesneleri, Spesifikasyon modeli ve depo arasında bir tür çaprazlamadır. Yukarıda söylediğim gibi, Spesifikasyon modeli yalnızca nerede cümlesi yönüyle ilgilenir, sorgunun birleştirmeler, alt seçmeler vb. Gibi diğer yönleriyle ilgilenmez. Depolar tüm sorguyla ilgilenir, ancak bir süre sonra kontrolden çıkar . Sorgu nesneleri de tüm sorguyla ilgilenir, ancak depoları sorgu nesnelerinin patlamalarıyla değiştirmek istemiyorum.


5
Harika soru. Ben de önerdiğimden daha fazla deneyime sahip insanların neler olduğunu görmek isterim. Genel deponun aynı zamanda Command nesneleri veya Query nesneleri için aşırı yüklemeleri de içerdiği bir kod tabanı üzerinde çalışıyorum, yapısı Ayende'nin blogunda anlattığına benzer. Not: Bu, programcıların da ilgisini çekebilir.
Simon Whitehead

LINQ bağımlılığını önemsemiyorsanız neden sadece IQueryable'ı ortaya çıkaran bir depo kullanmıyorsunuz? Yaygın bir yaklaşım genel bir havuzdur ve daha sonra yukarıdaki yeniden kullanılabilir mantığa ihtiyaç duyduğunuzda, ek yöntemlerinizle türetilmiş bir depo türü oluşturursunuz.
devdigital

@devdigital - Linq'e bağımlılık, veri uygulamasına bağımlılık ile aynı şey değildir. Linq'i nesnelere kullanmak istiyorum, böylece diğer iş katmanı işlevlerini sıralayabilir veya gerçekleştirebilirim. Ancak bu, veri modeli uygulamasına bağımlılık istediğim anlamına gelmez. Burada gerçekten bahsettiğim şey katman / katman arayüzü. Örnek olarak, bir sorguyu değiştirebilmek ve 200 yerde değiştirmek zorunda kalmamak istiyorum, ki bu, IQueryable'ı doğrudan iş modeline aktarırsanız olur.
Erik Funkenbusch

1
@devdigital - temelde bir depodaki sorunları iş katmanınıza taşır. Sadece sorunu karıştırıyorsun.
Erik Funkenbusch

Yanıtlar:


95

Feragatname: Henüz harika bir cevap olmadığından, bir süre önce okuduğum harika bir blog gönderisinin bir bölümünü neredeyse kelimesi kelimesine kopyalamaya karar verdim. Blog gönderisinin tamamını burada bulabilirsiniz . İşte burada:


Aşağıdaki iki arayüzü tanımlayabiliriz:

public interface IQuery<TResult>
{
}

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

IQuery<TResult>Belirtir o kullanıyor döndüren verilerle belirli sorgu tanımlayan bir mesaj TResultgenel tür. Önceden tanımlanmış arayüz ile aşağıdaki gibi bir sorgu mesajı tanımlayabiliriz:

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

Bu sınıf, iki parametreye sahip bir sorgu işlemini tanımlar ve bu, bir dizi Usernesne ile sonuçlanır . Bu mesajı işleyen sınıf şu şekilde tanımlanabilir:

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();
    }
}

Artık tüketicilerin genel IQueryHandlerarayüze güvenmesine izin verebiliriz :

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);
    }
}

Hemen bu model bize çok fazla esneklik sağlar, çünkü artık neyin enjekte edileceğine karar verebiliriz UserController. Tamamen farklı bir uygulama veya gerçek uygulamayı tamamlayan bir uygulamayı UserController(ve bu arayüzün diğer tüm tüketicilerinde) değişiklik yapmak zorunda kalmadan enjekte edebiliriz .

IQuery<TResult>Arayüz belirterek veya enjekte zaman bize destek zamanı derleme verir IQueryHandlersbizim kodunda. Biz değiştirdiğinizde FindUsersBySearchTextQuerydönmek için UserInfo[](uygulayarak yerine IQuery<UserInfo[]>), UserControllerüzerinde genel tür kısıtlaması beri, derlemek başarısız olur IQueryHandler<TQuery, TResult>haritaya mümkün olmayacaktır FindUsersBySearchTextQueryiçin User[].

IQueryHandlerArayüzü bir tüketiciye enjekte etmek , yine de ele alınması gereken daha az belirgin sorunlara sahiptir. Tüketicilerimizin bağımlılıklarının sayısı çok artabilir ve bir yapıcı çok fazla argüman aldığında, yapıcıya aşırı enjeksiyon yapılmasına neden olabilir. Bir sınıfın yürüttüğü sorguların sayısı sık sık değişebilir ve bu da yapıcı bağımsız değişkenlerinin sayısında sürekli değişiklikler gerektirir.

IQueryHandlersFazladan bir soyutlama katmanı ile çok fazla enjekte etmek zorunda kalma sorununu çözebiliriz . Tüketiciler ve sorgu işleyicileri arasında oturan bir aracı oluşturuyoruz:

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

IQueryProcessorBir genel yöntemi ile bir genel olmayan bir arabirimdir. Arayüz tanımında görebileceğiniz gibi, arayüze IQueryProcessorbağlıdır IQuery<TResult>. Bu, tüketicilerimizde IQueryProcessor. Yeniyi UserControllerkullanmak için yeniden yazalım 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);
    }
}

UserControllerArtık bağlıdır IQueryProcessoreden tüm sorgular işleyebileceği. UserControllerBireyin SearchUsersYöntem için, IQueryProcessor.Processbaşlatılan bir sorgu nesnesinde geçen yöntem. Yana FindUsersBySearchTextQueryuygulayan IQuery<User[]>arayüzü, biz jenerik için iletebilirsiniz Execute<TResult>(IQuery<TResult> query)yöntemle. C # tür çıkarımı sayesinde, derleyici genel türü belirleyebilir ve bu, türü açıkça belirtmek zorunda kalmamızı sağlar. ProcessYöntemin dönüş türü de bilinmektedir.

IQueryProcessorDoğru bulmak artık uygulamanın sorumluluğundadır IQueryHandler. Bu, biraz dinamik yazım ve isteğe bağlı olarak Bağımlılık Enjeksiyon çerçevesinin kullanılmasını gerektirir ve tümü yalnızca birkaç satır kodla yapılabilir:

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);
    }
}

QueryProcessorSınıf, belirli yapıları IQueryHandler<TQuery, TResult>sağlanan sorgu örneğinin türüne göre türü. Bu tür, sağlanan kapsayıcı sınıfından bu türden bir örnek almasını istemek için kullanılır. Maalesef, Handleyöntemi yansıma kullanarak çağırmamız gerekiyor (bu durumda C # 4.0 dymamic anahtar sözcüğünü kullanarak), çünkü bu noktada eylemci örneğini çevirmek imkansız çünkü genel TQueryargüman derleme zamanında mevcut değil. Ancak, Handleyöntem yeniden adlandırılmadıkça veya başka argümanlar almadıkça , bu çağrı asla başarısız olmaz ve isterseniz bu sınıf için bir birim testi yazmak çok kolaydır. Yansıma kullanmak hafif bir düşüş sağlar, ancak gerçekten endişelenecek bir şey değildir.


Endişelerinizden birini yanıtlamak için:

Bu yüzden, tüm sorguyu kapsayan alternatifler arıyorum, ancak yine de yeterince esnek bir komut sınıfları patlaması için spagetti Depolarını değiştirmeyeceksiniz.

Bu tasarımı kullanmanın bir sonucu, sistemde çok sayıda küçük sınıf olacağıdır, ancak çok sayıda küçük / odaklanmış sınıfa (açık isimlerle) sahip olmak iyi bir şeydir. Bu yaklaşım, bir sorgu sınıfında bunları gruplayabileceğinizden, bir depodaki aynı yöntem için farklı parametrelere sahip çok sayıda aşırı yüklemeye sahip olmaktan çok daha iyidir. Dolayısıyla, bir depodaki yöntemlerden çok daha az sorgu sınıfı elde edersiniz.


2
Görünüşe göre ödülü aldın. Kavramları seviyorum, sadece birinin gerçekten farklı bir şey sunmasını umuyordum. Tebrikler.
Erik Funkenbusch

1
@FuriCuri, tek bir sınıf gerçekten 5 sorguya ihtiyaç duyar mı? Belki buna çok fazla sorumluluğu olan bir sınıf olarak bakabilirsiniz. Alternatif olarak, sorgular toplanıyorsa, belki de aslında tek bir sorgu olmalıdır. Elbette bunlar sadece öneriler.
Sam

1
@stakx İlk örneğimde arayüzün genel TResultparametresinin IQuerykullanışlı olmadığı konusunda kesinlikle haklısınız . Ancak, güncellenmiş yanıtımda, TResultparametre, çalışma zamanında çözümlemek için Processyöntemi tarafından kullanılıyor . IQueryProcessorIQueryHandler
david.s

1
Ayrıca çok benzer bir uygulamaya sahip bir blogum var ve bu da beni doğru yolda olduğumu gösteriyor, bu bağlantı jupaol.blogspot.mx/2012/11/… ve bir süredir PROD uygulamalarında kullanıyorum, ancak bu yaklaşımla ilgili bir sorun yaşadım. Sorguları zincirleme ve yeniden kullanma Daha karmaşık sorgular oluşturmak için birleştirilmesi gereken birkaç küçük sorgum olduğunu varsayalım , sadece kodu kopyaladım ama daha iyi ve daha temiz bir yaklaşım arıyorum. Herhangi bir fikir?
Jupaol

4
@Cemre Sorgularımı Uzantı yöntemlerinde sarmaladım IQueryableve koleksiyonu numaralandırmadığımdan emin olduktan sonra QueryHandlersorguları aradım / zincirledim. Bu bana sorgularımı birim test etme ve zincirleme esnekliği sağladı. Üstümde bir uygulama hizmetim var QueryHandlerve denetleyicim, işleyici yerine doğrudan hizmetle konuşmaktan sorumlu
Jupaol

4

Bununla başa çıkma şeklim aslında basittir ve ORM agnostiktir. Depo için benim görüşüm şudur: Deponun görevi, uygulamaya bağlam için gerekli modeli sağlamaktır, böylece uygulama depoya ne istediğini sorar, ancak nasıl elde edeceğini söylemez .

Depo yöntemini, sorgu oluşturmak için depo tarafından kullanılacak bir Kriter (evet, DDD stili) sağlıyorum (veya ne gerekiyorsa - bir web hizmeti talebi olabilir). Birleşimler ve gruplar, neyin değil nasıl olduğunun ayrıntılarıdır ve bir kriter, bir where cümlesi oluşturmak için yalnızca temel olmalıdır.

Model = uygulama tarafından ihtiyaç duyulan nihai nesne veya veri yapısı.

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

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

Muhtemelen isterseniz ORM kriterlerini (Nhibernate) doğrudan kullanabilirsiniz. Depo uygulaması, Kriterleri temel alınan depolama veya DAO ile nasıl kullanacağını bilmelidir.

Etki alanınızı ve model gereksinimlerini bilmiyorum, ancak uygulamanın sorguyu kendisi oluşturmasının en iyi yolu ise garip olurdu. Model o kadar çok değişiyor ki kararlı bir şey tanımlayamıyor musunuz?

Bu çözüm açıkça bazı ek kodlar gerektiriyor ancak geri kalanını bir ORM'ye veya depolamaya erişmek için kullandığınız her şeye bağlamıyor. Depo bir cephe görevi görmek için görevini yerine getirir ve IMO temizdir ve 'kriter çevirisi' kodu yeniden kullanılabilir


Bu, depo büyümesi ve çeşitli türden verileri döndürmek için sürekli genişleyen bir yöntem listesine sahip olma sorunlarını ele almaz. Bununla ilgili bir sorun görmeyebileceğinizi anlıyorum (çoğu insan görmez), ancak diğerleri bunu farklı görüyor (Bağlantı verdiğim makaleyi okumanızı öneririm, benzer fikirlere sahip birçok insan var).
Erik Funkenbusch

1
Bunu ele alıyorum çünkü kriterler birçok yöntemi gereksiz kılıyor. Elbette, ihtiyacınız olan şey hakkında hiçbir şey bilmeden pek bir şey söyleyemem. Doğrudan db'yi sorgulamak istediğinize rağmen, muhtemelen bir depo tam da yolun üzerindedir. İlişkisel kaynakla doğrudan çalışmanız gerekiyorsa, doğrudan bunun için gidin, bir depoya gerek yok. Ve bir not olarak, Ayende'nin o gönderiyle kaç kişinin alıntı yaptığına dair sinir bozucu. Buna katılmıyorum ve birçok geliştiricinin modeli yanlış şekilde kullandığını düşünüyorum.
MikeSW

1
Sorunu bir şekilde azaltabilir, ancak yeterince büyük bir uygulama verildiğinde, yine de canavar depoları oluşturacaktır. Ayende'nin nHibernate'i doğrudan ana mantıkta kullanma çözümüne katılmıyorum, ancak kontrol dışı depo büyümesinin saçmalığı konusunda ona katılıyorum. Veritabanını doğrudan sorgulamak istemiyorum, ancak sorunu bir depodan sorgu nesnelerinin patlamasına da taşımak istemiyorum.
Erik Funkenbusch

2

Bunu yaptım, destekledim ve bunu geri aldım.

En büyük sorun şudur: Nasıl yaparsanız yapın, eklenen soyutlama size bağımsızlık kazandırmaz. Tanım gereği sızacaktır. Esasen, kodunuzun sevimli görünmesini sağlamak için bütün bir katman icat ediyorsunuz ... ancak bakımı azaltmaz, okunabilirliği artırmaz veya size herhangi bir model bilinemezliği kazandırmaz.

İşin eğlenceli yanı, Olivier'in cevabına kendi sorunuzu yanıtlamış olmanızdır: "Bu, Linq'ten aldığınız tüm faydalar olmadan Linq'in işlevselliğini kopyalamaktır".

Kendinize sorun: nasıl olmaz?


Linq'i iş katmanınıza entegre etme sorunlarını kesinlikle yaşadım. Çok güçlüdür, ancak veri modeli değişiklikleri yaptığımızda bu bir kabus olur. İş katmanını çok fazla etkilemeden yerelleştirilmiş bir yerde değişiklikler yapabildiğim için (değişiklikleri desteklemek için iş katmanını da değiştirmeniz gereken durumlar dışında) depolar ile işler iyileştirilir. Ancak, depolar, SRP'yi büyük ölçüde ihlal eden bu şişirilmiş katmanlar haline gelir. Demek istediğini anlıyorum, ama bu da herhangi bir sorunu çözmüyor.
Erik Funkenbusch

Veri katmanınız LINQ kullanıyorsa ve veri modeli değişiklikleri iş katmanınızda değişiklik yapılmasını gerektiriyorsa ... doğru katman yapmıyorsunuz demektir.
Stu

Artık o katmanı eklemediğini söylediğini sanıyordum. Eklenen soyutlamanın size hiçbir şey kazandırmadığını söylediğinizde, bu, nHibernate oturumunu (veya EF bağlamını) doğrudan iş katmanınıza geçirme konusunda Ayende ile hemfikir olduğunuzu gösterir.
Erik Funkenbusch

1

Akıcı bir arayüz kullanabilirsiniz. Temel fikir, bir sınıfın yöntemlerinin bir eylem gerçekleştirdikten sonra bu sınıfın mevcut örneğini döndürmesidir. Bu, yöntem çağrılarını zincirlemenize olanak tanır.

Uygun bir sınıf hiyerarşisi oluşturarak, erişilebilir yöntemlerin mantıksal akışını oluşturabilirsiniz.

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;
    }
}

Buna böyle derdin

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

Yalnızca yeni bir örneğini oluşturabilirsiniz Query. Diğer sınıfların korumalı bir kurucusu vardır. Hiyerarşinin amacı, yöntemleri "devre dışı bırakmak" tır. Örneğin, GroupByyöntem GroupedQuery, temel sınıfı olan Queryve bir Whereyönteme sahip olmayan bir döndürür (yöntem nerede bildirilir Query). Bu nedenle daha Wheresonra aramak mümkün değildir GroupBy.

Ancak mükemmel değil. Bu sınıf hiyerarşisi ile art arda üyeleri gizleyebilir, ancak yenilerini gösteremezsiniz. Bu nedenle Having, daha önce çağrıldığında bir istisna atar GroupBy.

WhereBirkaç kez aramanın mümkün olduğunu unutmayın . Bu AND, mevcut koşullara bir ile yeni koşullar ekler . Bu, tek koşullardan programlı olarak filtreler oluşturmayı kolaylaştırır. Aynısı ile de mümkündür Having.

Alan listelerini kabul eden yöntemlerin bir parametresi vardır params string[] fields. Tek alan adlarını veya bir dize dizisi geçirmenize izin verir.


Akıcı arayüzler çok esnektir ve farklı parametre kombinasyonları ile çok fazla yöntem aşırı yüklemesi oluşturmanızı gerektirmez. Örneğim dizelerle çalışıyor, ancak yaklaşım diğer türlere genişletilebilir. Özel durumlar için önceden tanımlanmış yöntemler veya özel türleri kabul eden yöntemler de bildirebilirsiniz. Ayrıca gibi yöntemler ekleyebilir ExecuteReaderveya ExceuteScalar<T>. Bu, bunun gibi sorguları tanımlamanıza izin verir

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

Bu şekilde oluşturulmuş SQL komutları bile komut parametrelerine sahip olabilir ve böylece SQL enjeksiyon sorunlarını önleyebilir ve aynı zamanda komutların veritabanı sunucusu tarafından önbelleğe alınmasına izin verebilir. Bu, O / R eşleştiricisinin yerine geçmez, ancak komutları aksi takdirde basit dize birleştirme kullanarak oluşturacağınız durumlarda yardımcı olabilir.


3
Hmm .. İlginç, ancak çözümünüzün SQL Enjeksiyon olasılıkları ile sorunları var gibi görünüyor ve önceden derlenmiş yürütme için hazırlanmış ifadeler oluşturmuyor (dolayısıyla daha yavaş çalışıyor). Muhtemelen bu sorunları çözmek için uyarlanabilir, ancak daha sonra türden güvenli olmayan veri kümesi sonuçlarına takılıp kaldık ve ne yapmadık. ORM tabanlı bir çözümü tercih ederim ve belki bunu açıkça belirtmeliyim. Bu, Linq'ten aldığınız tüm faydalar olmadan Linq'in işlevselliğini kopyalamaktır.
Erik Funkenbusch

Bu sorunların farkındayım. Bu sadece hızlı ve kirli bir çözüm, akıcı bir arayüzün nasıl oluşturulabileceğini gösteriyor. Gerçek dünya çözümünde, muhtemelen mevcut yaklaşımınızı ihtiyaçlarınıza göre uyarlanmış akıcı bir arayüzde "pişirirsiniz".
Olivier Jacot-Descombes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.