Denetleyicilerinizde SQL'den kaçınma stratejileri… veya Modellerimde kaç yöntem kullanmalıyım?


17

Dolayısıyla, oldukça sık karşılaştığım bir durum, modellerimin ikisinden birinin başladığı durumdur:

  • Tonlarca ve tonlarca yöntemle canavarlara dönüş

VEYA

  • Onlara SQL parçaları geçirmenize izin verin, böylece bir milyon farklı yöntem gerektirmeyecek kadar esnekler.

Örneğin, bir "widget" modelimiz olduğunu varsayalım. Bazı temel yöntemlerle başlıyoruz:

  • olsun ($ id)
  • insert ($ kayıt)
  • güncelleme ($ id, $ record)
  • Silme ($ id)
  • getList () // Widget'ların listesini alma

Hepsi iyi ve züppe, ama sonra bazı raporlara ihtiyacımız var:

  • listCreatedBetween ($ start_date, $ end_date)
  • listPurchasedBetween ($ start_date, $ end_date)
  • listOfPending ()

Ve sonra raporlama karmaşıklaşmaya başlar:

  • listPendingCreatedBetween ($ başlangıç_tarihi, $ bitiş_tarihi)
  • listForCustomer ($ CUSTOMER_ID)
  • listPendingCreatedBetweenForCustomer ($ customer_id, $ start_date, $ end_date)

Bunun nerede büyüdüğünü görebilirsiniz ... nihayet o kadar çok özel sorgu gereksinimimiz var ki ya tonlarca ve tonlarca yöntem uygulamak zorundayım ya da bir tür "sorgu" nesnesi geçirebileceğim tek bir sorgu> sorgu (sorgu $ query) yöntemi ...

... ya da mermiyi ısırıp böyle bir şey yapmaya başlayın:

  • list = MyModel-> query ("başlangıç_tarihi> X VE bitiş_tarihi <Y VE beklemede = 1 VE müşteri_kimliği = Z")

50 milyon daha spesifik yöntem yerine böyle bir yönteme sahip olmak için belirli bir cazibe var ... ama bazen temel olarak SQL'in denetleyiciye bir yığınını doldurması bazen yanlış geliyor.

Böyle durumları ele almanın "doğru" bir yolu var mı? Bu tür sorguları genel -> query () yöntemine doldurmak kabul edilebilir mi?

Daha iyi stratejiler var mı?


Aynı sorunu şu anda MVC olmayan bir projede yaşıyorum. Soru, veri erişim katmanının saklanan her prosedürü özetlemesi ve iş mantığı katmanı veritabanını agnostik bırakması veya veri erişim katmanının, temel veritabanı hakkında bir şeyler bilerek iş katmanı pahasına genel olması gerektiği konusunda ortaya çıkıyor mu? Belki de bir ara çözüm ExecuteSP (string spName, params object [] parametreleri) gibi bir şeye sahip olmak, daha sonra iş katmanının okuması için bir yapılandırma dosyasına tüm SP isimlerini eklemektir. Buna gerçekten çok iyi bir cevabım yok.
Greg Jackson

Yanıtlar:


10

Martin Fowler'in Kurumsal Uygulama Mimarisi Desenleri, önerdiğim şey olan Sorgu Nesnesinin kullanımı da dahil olmak üzere ORM ile ilgili bir dizi patlayıcıyı açıklar.

Sorgu nesneleri, her sorgu için mantığı ayrı ayrı yönetilen ve sürdürülen strateji nesnelerine ayırarak Tek Sorumluluk ilkesini izlemenizi sağlar. Denetleyiciniz kullanımlarını doğrudan yönetebilir veya ikincil bir denetleyiciye veya yardımcı nesneye devredebilir.

Birçoğunuz olacak mı? Kesinlikle. Bazıları genel sorgularda gruplandırılabilir mi? Evet tekrar.

Meta verilerden nesneler oluşturmak için bağımlılık enjeksiyonu kullanabilir misiniz? Çoğu ORM aracının yaptığı şey budur.


4

Bunu yapmanın doğru bir yolu yok. Birçok insan ORM'leri tüm karmaşıklığı ortadan kaldırmak için kullanır. Daha gelişmiş ORM'lerden bazıları kod ifadelerini karmaşık SQL ifadelerine dönüştürür. ORM'lerin dezavantajları vardır, ancak birçok uygulama için faydalar maliyetlerden ağır basar.

Büyük bir veri kümesiyle çalışmıyorsanız, yapılacak en basit şey tüm tabloyu belleğe seçmek ve kodda filtrelemektir.

//pseudocode
List<Person> people = Sql.GetList<Person>("select * from people");
List<Person> over21 = people.Where(x => x.Age >= 21);

Dahili raporlama uygulamaları için bu yaklaşım muhtemelen iyidir. Veri kümesi gerçekten büyükse, tablonuzda çok sayıda özel yöntemin yanı sıra uygun dizinlere de ihtiyacınız olacaktır.


1
+ 1 "Bunu yapmanın doğru bir yolu yok"
ozz

1
Ne yazık ki, veri kümesinin dışına filtre uygulamak, birlikte çalıştığımız en küçük veri kümeleriyle bile gerçekten bir seçenek değil - çok yavaş. :-( Diğerlerinin de aynı sorunla karşılaştıklarını duymak güzel. :-)
Keith Palmer Jr.

@KeithPalmer meraktan kaçtı, masalarınız ne kadar büyük?
dan

Yüz binlerce satır, daha fazla değilse. Veritabanının uygulama ile aynı makinede olmadığı dağıtılmış bir mimariyle, özellikle veritabanı dışında kabul edilebilir performansla filtre uygulamak için çok fazla.
Keith Palmer

-1 için "Bunu yapmanın doğru bir yolu yok". Birkaç doğru yol vardır. OP'nin yaptığı gibi bir özellik eklediğinizde yöntem sayısını ikiye katlamak ölçeklenemez bir yaklaşımdır ve burada önerilen alternatif, sorgu özelliklerinin sayısı yerine veritabanı boyutuyla ilgili olarak eşit derecede ölçeklendirilemez. Ölçeklenebilir yaklaşımlar mevcuttur, diğer cevaplara bakınız.
Theodore Murdock

4

Bazı ORM'ler, temel yöntemlerden başlayarak karmaşık sorgular oluşturmanıza olanak tanır. Örneğin

old_purchases = (Purchase.objects
    .filter(date__lt=date.today(),type=Purchase.PRESENT).
    .excude(status=Purchase.REJECTED)
    .order_by('customer'))

Django ORM'de mükemmel geçerli bir sorgudur .

Fikir, Purchase.objectsiç durumu bir sorgu hakkındaki bilgileri temsil eden bazı sorgu oluşturucunuz (bu durumda ) olmasıdır. Gibi yöntemler get, filter, exclude, order_bygeçerlidir ve güncellenmiş durum ile yeni bir sorgu oluşturucu döndürür. Bu nesneler yinelenebilir bir arabirim uygular, böylece bunları yinelediğinizde sorgu gerçekleştirilir ve şimdiye kadar oluşturulmuş sorgunun sonuçlarını alırsınız. Bu örnek Django'dan alınmış olmasına rağmen, aynı yapıyı diğer birçok ORM'de de göreceksiniz.


Bunun old_purchases = Purchases.query ("date> date.today () AND type = Purchase.PRESENT AND status! = Purchase.REJECTED") gibi bir şeye göre ne avantajı olduğunu görmüyorum; SQL AND'leri ve OR'leri yöntem AND'lere ve OR'lara dönüştürerek karmaşıklığı azaltmaz veya hiçbir şeyi soyutlamazsınız - sadece AND'lerin ve OR'lerin temsilini değiştiriyorsunuz, değil mi?
Keith Palmer Jr.6

4
Aslında değil. Size birçok avantaj sağlayan SQL'i soyutluyorsunuz. İlk olarak, enjeksiyondan kaçının. Ardından, ORM bunu sizin için hallettiğinden, SQL lehçesinin biraz farklı sürümleri hakkında endişelenmeden temeldeki veritabanını değiştirebilirsiniz. Çoğu durumda, fark etmeden bir NoSQL arka ucu da koyabilirsiniz. Üçüncü olarak, bu sorgu oluşturucular, başka bir şey gibi aktarabileceğiniz nesnelerdir. Bu, modelinizin sorgunun yarısını oluşturabileceği anlamına gelir (örneğin, en yaygın durumlar için bazı yöntemlere sahip olabilirsiniz) ve daha sonra denetleyicide işlenebilir ..
Andrea

2
... en özel durumlar. Tipik bir örnek, Django'daki modeller için varsayılan bir sipariş tanımlamaktır. Aksi belirtilmedikçe tüm sorgu sonuçları bu sıralamayı izler. Dördüncüsü, performans nedeniyle verilerinizi normalleştirmeniz gerekmiyorsa, tüm sorgularınızı yeniden yazmak yerine ORM'yi değiştirmeniz yeterlidir.
Andrea

+1 Belirtilen gibi dinamik sorgu dilleri ve LINQ için.
Evan Plaice

2

Üçüncü bir yaklaşım var.

Spesifik örneğiniz, gerekli özelliklerin sayısı arttıkça ihtiyaç duyulan yöntem sayısında katlanarak büyüme gösterir: her sorgu özelliğini birleştirerek gelişmiş sorgular sunma yeteneği istiyoruz ... bunu, yöntemler ekleyerek bir yöntem için temel sorgu, isteğe bağlı bir özellik eklersek iki, iki eklersek dört, üç eklersek sekiz, n özellik eklersek 2 ^ n.

Bu, üç veya dört özelliğin ötesinde açıkça görülemez ve yöntemler arasında neredeyse kopyalanmış olan yakından ilişkili bir çok kodun kötü bir kokusu var.

Parametreleri tutmak için bir veri nesnesi ekleyerek ve sağlanan (veya sağlanmayan) parametreler kümesine göre sorguyu oluşturan tek bir yöntemle bunu önleyebilirsiniz. Bu durumda, tarih aralığı gibi yeni bir özellik eklemek, veri nesnenize tarih aralığı için ayarlayıcılar ve alıcılar eklemek ve ardından parametrelenmiş sorgunun oluşturulduğu yere biraz kod eklemek kadar basittir:

if (dataObject.getStartDate() != null) {
    query += " AND (date BETWEEN ? AND ?) "
}

... ve parametrelerin sorguya eklendiği yer:

if (dataObject.getStartDate() != null) {
    preparedStatement.setTime(dataObject.getStartDate());
    preparedStatement.setTime(dataObject.getEndDate());
}

Bu yaklaşım, özellikler eklendikçe, rasgele, parametrelenmemiş sorgulara izin vermek zorunda kalmadan doğrusal kod büyümesine izin verir.


0

Bence genel fikir birliği, MVC'deki modellerinizde mümkün olduğunca veri erişimini korumaktır. Diğer tasarım prensiplerinden biri, daha genel sorgularınızın (modelinizle doğrudan ilişkili olmayan) bazılarını, diğer modeller tarafından da kullanılmasına izin verebileceğiniz daha yüksek, daha soyut bir seviyeye taşımaktır. (RoR'de çerçeve olarak adlandırılan bir şeyimiz var) Dikkate almanız gereken başka bir şey daha var ve bu da kodunuzun bakımıdır. Projeniz büyüdükçe, kontrol cihazlarında veri erişiminiz varsa, onu izlemek giderek zorlaşacaktır (Şu anda büyük bir projede bu sorunla karşı karşıyayız) Modeller, yöntemlerle dağınık olmasına rağmen, herhangi bir kontrolör için tek bir temas noktası sağlar. tablolardan sorgulama yapabilir. (Bu, aynı zamanda yararlı olan bir kodun yeniden kullanılmasına da neden olabilir)


1
Neden bahsettiğinize bir örnek ...?
Keith Palmer Jr.6

0

Hizmet katmanı arabiriminizin birçok yöntemi olabilir, ancak veritabanına yapılan çağrıda yalnızca bir tane olabilir.

Bir veritabanında 4 büyük işlem vardır

  • Ekle
  • Güncelleme
  • Sil
  • Sorgu

Başka bir isteğe bağlı yöntem, temel DB işlemleri kapsamında olmayan bazı veritabanı işlemlerini yürütmek olabilir. Buna Execute diyelim.

Ekleme ve Güncellemeler Kaydet adı verilen tek bir işlemde birleştirilebilir.

Yöntemlerinizin birçoğu sorgudur. Böylece, acil ihtiyaçların çoğunu karşılamak için genel bir arayüz oluşturabilirsiniz. Örnek bir genel arayüz:

 public interface IDALService
    {
        DataTransferObject<T> Save<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Search<T>(DataTransferObject<T> Dto) where T: IPOCO;
        DataTransferObject<T> Delete<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Execute<T>(DataTransferObject<T> Dto) where T : IPOCO;
    }

Veri aktarım nesnesi geneldir ve içinde tüm filtreleriniz, parametreleriniz, sıralama vb. Veri katmanı bunun ayrıştırılmasından ve çıkarılmasından ve işlemin saklı yordamlar, parametreli sql, linq vb. Yoluyla veritabanına ayarlanmasından sorumlu olacaktır. Bu nedenle, katmanlar arasında SQL aktarılmaz. Bu genellikle bir ORM'nin yaptığı şeydir, ancak kendinizinkini oluşturabilir ve kendi eşlemenize sahip olabilirsiniz.

Yani, sizin durumunuzda Widget'larınız var. Widget'lar IPOCO arabirimini uygular.

Yani, hizmet katmanı modelinizde getList().

Dönüşüm getListsağlamak için bir eşleme katmanı gerekir

Search<Widget>(DataTransferObject<Widget> Dto)

ve tam tersi. Diğerlerinin de belirttiği gibi, bu bazen bir ORM yoluyla yapılır, ancak sonuçta özellikle 100'lü tablolarınız varsa çok sayıda kazan plakası türü kodla sonuçlanırsınız. ORM sihirli bir şekilde parametreli SQL oluşturur ve bunu veritabanında çalıştırır. Kendinizi yuvarlarsanız, ek olarak veri katmanının kendisinde, SP, linq vb. (Temelde veritabanına giden sql) kurmak için haritacılara ihtiyaç olacaktır.

Daha önce de belirtildiği gibi, DTO kompozisyon tarafından oluşturulan bir nesnedir. Belki de içindeki nesnelerden biri QueryParameters adı verilen bir nesnedir. Bunlar, sorgu tarafından ayarlanacak ve kullanılacak sorgu için tüm parametreler olacaktır. Başka bir nesne, querys, updates, ext adresinden döndürülen nesnelerin listesi olacaktır. Bu yük. Bu durumda faydalı yük Widget listesi olacaktır.

Yani, temel strateji:

  • Hizmet Katmanı Çağrıları
  • Bir tür Depo / Eşleme kullanarak Hizmet Katmanı Çağrısını Veritabanına Dönüştürme
  • Veritabanı Çağrısı

Sizin durumunuzda, modelin birçok yöntemi olabileceğini düşünüyorum, ancak optimal olarak veritabanı çağrısının genel olmasını istiyorsunuz. Yine de sizin için dinamik olarak parametrelenmiş SQL'i yaratan çok sayıda kazan plakası eşleme kodu (özellikle SP'lerle) veya büyülü ORM koduyla sonuçlanırsınız.

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.