ORM mantığını içine almak için depo şablonuna alternatifler?


24

Sadece bir ORM değiştirmek zorunda kaldım ve bu oldukça korkutucu bir görevdi çünkü sorgu mantığı her yerde sızdırıyordu. Eğer yeni bir uygulama geliştirmek zorunda olsaydım, kişisel tercihim değişiklik için geleceği korumak için tüm sorgu mantığını (bir ORM kullanarak) kapsüllemek olurdu. Depo deseni kodlama ve sürdürme konusunda oldukça zahmetlidir, bu yüzden sorunu çözmek için başka bir kalıp olup olmadığını merak ediyordum?

Gerçekte gerekmeden önce fazladan karmaşıklık eklememek, çevik olmak, vb. İle ilgili gönderiler öngörebilirim ancak yalnızca benzer bir sorunu daha basit bir şekilde çözen mevcut kalıplarla ilgileniyorum.

İlk düşüncem, uzatma yöntemleri aracılığıyla belirli tip depo sınıflarına gereken yöntemleri eklediğim, ancak statik test ünitesinin statik yöntemleri çok ağrılı olduğu genel bir tür depoya sahip olmaktı. IE:

public static class PersonExtensions
{
    public static IEnumerable<Person> GetRetiredPeople(this IRepository<Person> personRep)
    {
        // logic
    }
}

5
Kodlama ve bakımını yapmakta sıkıntı verici bulduğunuz Depo modeli hakkında tam olarak nedir?
Matthew Flynn

1
Orada alternatifler var. Bunlardan bir tanesi, kullanmamış olmama rağmen iyi bir yaklaşım gibi görünmesine rağmen, Query Object desenini kullanmak. Depo IMHO, doğru kullanılırsa, hepsi kullanılır ve hepsi olmasa da iyi bir kalıptır
dreza

Yanıtlar:


14

Her şeyden önce: Genel bir depo, temel bir sınıf olarak düşünülmeli ve uygulamaları tamamlamamalıdır. Ortak CRUD yöntemlerini uygulamaya koymanıza yardımcı olacaktır. Ancak yine de sorgu yöntemlerini uygulamanız gerekir:

public class UserRepository : EntityFrameworkRepository<User>, IUserRepository
{
    public UserRepository(DbContext context){}

    public ICollection<Person> FindRetired()
    {
        return context.Persons.Where(p => p.Status == "Retired").ToList();
    }
}

İkinci:

Geri dönmeyin IQueryable. Sızdıran bir soyutlama. Bu arayüzü kendiniz uygulamaya çalışın veya altta yatan db sağlayıcısı hakkında bilgi sahibi olmadan kullanın. Örneğin, her db sağlayıcısının istekli yüklenen varlıklar için kendi API'si vardır. Bu yüzden sızdıran bir soyutlama.

Alternatif

Alternatif olarak, sorguları kullanabilirsiniz (örneğin, Komut / Sorgu ayırma düzeninde tanımlandığı gibi). CQS wikipedia'da tanımlanmıştır.

Temel olarak, çağırdığınız sorgu sınıflarını yaratırsınız:

public class GetMyMessages
{
    public GetMyMessages(IDbConnection connection)
    {
    }


    public Message[] Execute(DateTime minDate)
    {
        //query
    }
}

Sorguların en iyi yanı, kodun karışıklığını bozmadan farklı sorgular için farklı veri erişim yöntemleri kullanabilmenizdir. örneğin, bir web servisini birinde kullanabilirsiniz, başkalarında görevlendirir ve üçte birinde ADO.NET kullanabilirsiniz.

Bir .NET uygulamasıyla ilgileniyorsanız makalemi okuyun: http://blog.gauffin.org/2012/10/griffin-decoupled-the-queries/


Alternatif kalıba önermediniz Daha büyük projelerde daha fazla karmaşa yaratmaz mıydınız?
Dante

3
Küçük sınıflara sahip olma tanımınız karışıklıksa, evet. Bu durumda, SOLID ilkelerini izlemeye çalışmamalısınız, çünkü bunları uygulamak her zaman daha fazla (daha küçük) sınıflar üretecektir.
jgauffin

2
Daha küçük sınıflar daha iyidir, ancak mevcut sorgu sınıflarının navigasyonu, gerekli işlevselliğin orada olup olmadığını anlamak için uygulamak için bir (etki alanı) deposunun intellisense'ine göz atmaktan daha zahmetli olmaz.
Dante

4
Proje yapısı daha önemli hale gelir. Tüm sorguları gibi bir ad alanına koyarsanız YourProject.YourDomainModelName.Queriesgezinmesi kolay olacaktır.
jgauffin

CQS ile iyi bir şekilde yapmak zor buluyorum bir şey ortak sorgular. Örneğin, bir kullanıcıya verilen bir sorgu tüm ilgili öğrencileri alır (farklı sınıflar, kendi çocukları vs.) Şimdi başka bir sorgunun bir kullanıcı için çocuk alması ve sonra bazı işlemler yapması gerekiyor - bu nedenle sorgu 2, sorgu 1'deki ortak mantığı kaldırabilir, ancak bunu yapmanın basit bir yolu yoktur. Bunu kolaylaştıran herhangi bir CQS uygulaması bulamadım. Depolar bu konuda çok yardımcı olur. Her iki modelin de bir hayranı değil, sadece payımı paylaşıyorum.
Şef,

8

İlk önce şunu söylüyorum, ORM zaten veritabanınız üzerinde yeterince büyük bir soyutlamadır. Bazı ORM'ler tüm ortak ilişkisel veritabanlarına bağlanma sağlar (NHibernate, MSSQL, Oracle, MySQL, Postgress vb. İçin bağlar vardır.) Bu yüzden üstüne yeni bir soyutlama yapmak bana karlı gelmiyor. Ayrıca, tartışarak, bu ORM'yi "soyutlamak" zorunda olmanız gerektiğini anlamsızdır.

Bu soyutlamayı hala yapmak istiyorsan, depo şablonuna karşı giderdim. Saf SQL çağında harikaydı, ancak modern ORM karşısında oldukça zahmetli. Esas olarak, çünkü CRUD işlemleri ve sorgulama gibi ORM özelliklerinin çoğunu yeniden uygularsınız.

Böyle soyutlamalar yapacak olsaydım, bu kuralları / kalıpları kullanırdım.

  • CQRS
  • Mevcut ORM özelliklerinden mümkün olduğu kadar kullanın
  • Yalnızca karmaşık mantığı ve sorguları kapsüller
  • ORM'nin "sıhhi tesisatını" bir mimarinin içinde gizlemeye çalışın

Somut uygulamada, ORM'nin CRUD operasyonlarını doğrudan, sarmadan kullanacaktım. Ayrıca doğrudan kodda basit sorgular yapardım. Ancak karmaşık sorgular kendi nesnelerinde saklanacaktır. ORM'yi "gizlemek" için, veri bağlamını şeffaf olarak bir servis / UI nesnelerine enjekte etmeye ve nesneleri sorgulamak için de aynısını yapmaya çalışacağım.

Söylemek istediğim son şey, birçok insanın ORM'yi nasıl kullanacağını ve bundan en iyi "karı" elde etmeyi bilmeden kullanmasıdır. Depo öneren kişiler genellikle bu türdendir.

Önerilen bir okuma olarak, Ayende'nin blogunu , özellikle de bu makaleyi söyleyebilirim .


+1 "Söylemek istediğim son şey, birçok kişinin ORM'yi nasıl kullanacağını ve bundan en iyi" karı "nasıl alacağını bilmeden kullanmasıdır. Depoları öneren insanlar genellikle bu türdendir"
Misters

1

Sızdıran uygulamadaki sorun, birçok farklı filtre koşuluna ihtiyaç duymanızdır.

Bir depo yöntemi FindByExample kullanıyorsanız ve bu şekilde kullanıyorsanız, bu api'yi daraltabilirsiniz.

// find all retired persons
Person filter = new Person {Status=PersonStatus.Retired};
IEnumerable<Person> found = personRepository.FindByExample(filter);


// find all persons who have a dog named "sam"
Person filter = new Person();
filter.AddPet(new Dog{Name="sam"});
IEnumerable<Person> found = personRepository.FindByExample(filter);

0

Uzantılarınızın Yerleşim yerine İQueryable üzerinde çalışmasını sağlayın.

public static class PersonExtensions
{
    public static IQueryable<Person> AreRetired(this IQueryable<Person> people)
    {
        return people.Where(p => p.Status == "Retired");
    }
}

Birim testine:

List<Person> people = new List<Person>();
people.Add(new Person() { Name = "Bob", Status = "Retired" });
people.Add(new Person() { Name = "Sam", Status = "Working" });

var retiredPeople = people.AsQueryable().AreRetired();
// assert that Bob is in the list
// assert that Sam is not in the list

Test için fan alayına gerek yoktur. Gerektiğinde daha karmaşık sorgular oluşturmak için bu uzantı yöntemlerini de birleştirebilirsiniz.


5
-1 a) IQueryablekötü bir anne veya daha çok bir GOD arayüzüdür. Uygulaması çok vardı ve çok az (varsa) SQl sağlayıcılarına tam LINQ var. b) Uzatma yöntemleri uzatma (temel OOP ilkelerinden biri) imkansız kılar.
jgauffin

0

Burada NHibernate için oldukça temiz bir sorgu nesnesi deseni yazdım: https://github.com/shaynevanasperen/NHibernate.Sessions.Operations

Bunun gibi bir arayüz kullanarak çalışır:

public interface IDatabases
{
    ISessionManager SessionManager { get; }

    T Query<T>(IDatabaseQuery<T> query);
    T Query<T>(ICachedDatabaseQuery<T> query);

    void Command(IDatabaseCommand command);
    T Command<T>(IDatabaseCommand<T> command);
}

Buna benzer bir POCO varlık sınıfı göz önüne alındığında:

class Database1Poco
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
}

Bu gibi sorgu nesneleri oluşturabilirsiniz:

class Database1PocoByProperty1 : DatabaseQuery<Database1Poco>
{
    public override Database1Poco Execute(ISessionManager sessionManager)
    {
        return sessionManager.Session.Query<Database1Poco>().SingleOrDefault(x => x.Property1 == Property1);
    }

    public int Property1 { get; set; }
}

Ve sonra onları bu şekilde kullanın:

var database1Poco = _databases.Query(new Database1PocoByProperty1 { Property1 = 1 });

1
Bu bağlantı soruyu cevaplayabilse de, cevabın temel kısımlarını buraya eklemek ve referans için bağlantıyı sağlamak daha iyidir. Bağlantılı sayfa değişirse, yalnızca bağlantı yanıtları geçersiz olabilir.
Dan Pichelman,

Özür dilerim, acelem vardı. Cevap, bazı örnek kodları göstermek için güncellendi.
Shayne,

1
Gönderiyi daha fazla içerikle geliştirmek için + 1'leyin.
Songo,
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.