Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I have used a blend of #2 and #3, but I prefer a strict generic repository if possible (stricter than even suggested in the link for #3). #1 is no good because it plays poorly with unit testing. </p> <p>If you have a smaller domain or need to constrict which entities that your domain allows to be queried, I suppose #2- or #3 that defines entity specific repository interfaces that themselves implement a generic repository- makes sense. However, I find it to be exhausting and unnecessary to write an interface and a concrete implementation for every entity I want to query. What good is <code>public interface IFooRepository : IRepository&lt;Foo&gt;</code> (again, unless I need to constrain developers to a set of allowed aggregate roots)?</p> <p>I just define my generic repository interface, with <code>Add</code>, <code>Remove</code>, <code>Get</code>, <code>GetDeferred</code>, <code>Count</code>, and <code>Find</code> methods (Find returns an <code>IQueryable</code> interface allowing LINQ), create a concrete generic implementation, and call it a day. I rely heavily on <code>Find</code> and thus LINQ. If I need to use a specific query more than once, I use extension methods and write the query using LINQ.</p> <p>This covers 95% of my persistence needs. If I need to perform some sort of persistence action that can't be done generically, I use a home-grown <code>ICommand</code> API. For example, say I'm working with NHibernate and I need to perform a complex query as part of my domain, or perhaps I need to do a bulk command. The API looks roughly like this:</p> <pre><code>// marker interface, mainly used as a generic constraint public interface ICommand { } // commands that return no result, or a non-query public interface ICommandNoResult : ICommand { void Execute(); } // commands that return a result, either a scalar value or record set public interface ICommandWithResult&lt;TResult&gt; : ICommand { TResult Execute(); } // a query command that executes a record set and returns the resulting entities as an enumeration. public interface IQuery&lt;TEntity&gt; : ICommandWithResult&lt;IEnumerable&lt;TEntity&gt;&gt; { int Count(); } // used to create commands at runtime, looking up registered commands in an IoC container or service locator public interface ICommandFactory { TCommand Create&lt;TCommand&gt;() where TCommand : ICommand; } </code></pre> <p>Now I can create an interface to represent a specific command.</p> <pre><code>public interface IAccountsWithBalanceQuery : IQuery&lt;AccountWithBalance&gt; { Decimal MinimumBalance { get; set; } } </code></pre> <p>I can create a concrete implementation and use raw SQL, NHibernate HQL, whatever, and register it with my service locator.</p> <p>Now in my business logic I can do something like this:</p> <pre><code>var query = factory.Create&lt;IAccountsWithBalanceQuery&gt;(); query.MinimumBalance = 100.0; var overdueAccounts = query.Execute(); </code></pre> <p>You can also use a Specification pattern with <code>IQuery</code> to build meaningful, user-input-driven queries, rather than having an interface with million confusing properties, but that assumes you don't find the specification pattern confusing in its own right ;).</p> <p>One last piece of the puzzle is when your repository needs to do specific pre- and -post repository operation. Now, you can very easily create an implementation of your generic repository for a specific entity, then override the relevant method(s) and do what you need to do, and update your IoC or service locator registration and be done with it.</p> <p>However, sometimes this logic is cross-cutting and awkward to implement by overriding a repository method. So I created <code>IRepositoryBehavior</code>, which is basically an event sink. (Below is just a rough definition off the top of my head)</p> <pre><code>public interface IRepositoryBehavior { void OnAdding(CancellableBehaviorContext context); void OnAdd(BehaviorContext context); void OnGetting(CancellableBehaviorContext context); void OnGet(BehaviorContext context); void OnRemoving(CancellableBehaviorContext context); void OnRemove(BehaviorContext context); void OnFinding(CancellableBehaviorContext context); void OnFind(BehaviorContext context); bool AppliesToEntityType(Type entityType); } </code></pre> <p>Now, these behaviors can be anything. Auditing, security checking, soft-delete, enforcing domain constraints, validation, etc. I create a behavior, register it with the IoC or service locator, and modify my generic repository to take in a collection of registered <code>IRepositoryBehavior</code>s, and check each behavior against the current repository type and wrap the operation in the pre/post handlers for each applicable behavior.</p> <p>Here's an example soft-delete behavior (soft-delete means that when someone asks to delete an entity, we just mark it as deleted so it can't be returned again, but is never actually physically removed).</p> <pre><code>public SoftDeleteBehavior : IRepositoryBehavior { // omitted public bool AppliesToEntityType(Type entityType) { // check to see if type supports soft deleting return true; } public void OnRemoving(CancellableBehaviorContext context) { var entity = context.Entity as ISoftDeletable; entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity. } } </code></pre> <p>Yes, this is basically a simplified and abstracted implementation of NHibernate's event listeners, but that's why I like it. A) I can unit test a behavior without bringing NHibernate into the picture B) I can use these behaviors outside of NHibernate (say the repository is client implementation that wraps REST service calls) C) NH's event listeners can be a real pain in the ass ;)</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload