Generic Repository

2019-03-22 05:42发布

I'm working on incorporating some common repository infrastructure into some applications that wrap EF, L2SQL and WCF Data Services (though the underlying Data Access implementations should be arbitrary). I've done some reading on the matter but can't seem to find an example that really satisfies.

I started with:

public interface IRepository : IDisposable
{
    IQueryable<T> Query<T>();
    void Attach(object entity);
    void ForDeletion(object entity);
    void SaveChanges();
}

But I like the idea of a repository with narrow domain contracts (http://codebetter.com/blogs/gregyoung/archive/2009/01/16/ddd-the-generic-repository.aspx). The above leaves the consumer to know all of the entity types supported by the repository.

I'm not going to say it's out of the question, but I'd be very hard pressed to be convinced that IQueryables themselves should not be part of the repository contract. I'm not a fan of boiler plate code and I firmly believe that the more you have, the more maintenance black holes you introduce. So, what I'm saying is that it would be very hard to convince me that anything that looks like:

 Public IEnumerable<Customer> GetCustomersWithFirstNameOf(string _Name) {
     internalGenericRepository.FetchByQueryObject(new CustomerFirstNameOfQuery(_Name)); //could be hql or whatever
 }

is anything but an utterly abysmal idea. What about when you want to search on first name and last name. Or first name or last name...etc. You end up with a repository with 1000+ operations, half of which repeat the same logic. NOTE: I'm NOT the calling code should be responsible for applying all filtering and such, but rather that having a complementary specification source makes sense to me:

public static class CustomerSpecifications 
{
    public IQueryable<Customer> WithActiveSubscriptions(this IQueryable<Customer> customers, DateTime? start, DateTime? end)
    {
        // expression manipulation
        return customers;
    }
}


// bind some data source
repository.GetQueryable().WithActiveSubscriptions();

Ok, so moving forward, I think having domain model explicit repositories sound like a good idea in the following format:

public interface IRepository : IDisposable
{
    void SaveChanges();
}

public interface IRepository<T>: IRepository
{
    IQueryable<T> GetQueryable();
    void Attach(T entity);
    void ForDeletion(T entity);
}

then

public class CustomerRepository:IRepository<Customer>
{
    private ObjectContext _context;
    // trivial implementation
}

but my problem with this is that it only allows me to delete Customers. What about the case where I want to delete a Customer's address? That is, I use the repository to query a Customer entity, but then want to delete myCustomer.CustomerAddresses[0]? I need to create a second repository simply to attach and delete the address I want?

I guess I could have my CustomerRepository be:

public class CustomerRepository:IRepository<Customer>, IRepository<CustomerAddress>
{
    private ObjectContext _context;
    // trivial implementation
}

which would let me reuse the repository to delete CustomerAddresses, but I'm not sure how I feel about inheriting IRepository<T> for every part of the graph I want to expose deletions for...

public class CustomerRepository:IRepository<Customer>, IRepository<CustomerAddress> /* this list may get pretty long, and then I really just have a masked EF ObjectContext, don't I? */
{

Anyone have any suggestions for a better implementation?

1条回答
做自己的国王
2楼-- · 2019-03-22 06:08

A lot of questions in there, and some of the answers might be subjective/opionated, but i'll roll with the punches.

IQueryable vs specific methods.

I actually agree with you. I don't like the idea of having lots and lots of methods for defining all the different operations on my repository. I prefer to allow the calling code to supply the specification. Now, my calling code is not my presentation - it's a domain service/BLL. So it's not like we're giving full power to the UI, we're just allowing our repository to stay very generic. DDD purists would argue that repositories are abstractions of your domain model, and therefore should serve specific domain operations. I'll leave that open for you to decide. Also, if your using IQueryable, then your forcing an "unknown" LINQ provider to be used. Not a problem for me, as i will always be using something that works off LINQ (Entity Framework, for example). But this is another problem that DDD-purists have.

What about when you want to search on first name and last name

Well, i use Expression<Func<T,bool>> for my domain services, so i can do this:

var customers = repository.FindAll<Customer>(x => x.FirstName == "Bob" && x.LastName == "Saget");

If you don't like that, you could use the Specification pattern to make use of AND/OR-type code.

Deleting customer address

Well generally speaking, you should have one repository per aggregate root. Without knowing your domain model, it sounds like "Customer" is an aggregate root, and "CustomerAddress" is an aggregate, which is always associated to a "Customer" (e.g cannot exist without one).

Therefore, there you don't require a CustomerAddressRepository. CustomerAddress operations should be done via your CustomerAddress repository.

This is why i like to create a GenericRepository<T> implementation of IRepository<T> which implements core operations on the aggregate (Find, Add, Remove), then even more specific implementations deriving from GenericRepository<T>.

Here's how i would handle Customer Repository. Create a GenericRepository<T>, which implements IRepository<T> core operations. Then create another interface for ICustomerRepository, which also implements IRepository<T>.

Now, create a implementation called CustomerRepository, which inherits the core repository implementation from GenericRepository<T>, and also extends this to allows extra operations (such as removing address).

public class CustomerRepository : GenericRepository<Customer>, ICustomerRepository
{
   // inherits, for example:
   // IQueryable<Customer> Query<Customer>();

   public void Remove(Address address)
   {
       // get the customer (if you havent already got it)
       var cust = ctx.Customers.SingleOrDefault(x => x.CustId == address.CustId);
       cust.Addresses.Remove(address);
   }
}

And your ICustomerRepository would look like this:

public interface ICustomerRepository : GenericRepository<Customer>, IRepository<Customer>
{
    // inherited from GenericRepository<Customer>
    //IQueryable<T> Query<T>();
    //void Attach(object entity);
    //void ForDeletion(object entity);
    //void SaveChanges();
    void Remove(Address address);
}

Know what i mean? Start with really generic repositories, then get more specific as you require.

You do not require a CustomerAddressRepository. Perform the operations via the CustomerRepository as i have shown above.

HTH.

查看更多
登录 后发表回答