Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>One possibly way might be using specifications with a base specification that checks the soft deleted flag for all queries together with an include strategy. </p> <p>I’ll illustrate an adjusted version of the specification pattern that I've used in a project (which had its origin in this <a href="http://blog.willbeattie.net/2011/02/specification-pattern-entity-framework.html" rel="nofollow">blog post</a>)</p> <pre><code>public abstract class SpecificationBase&lt;T&gt; : ISpecification&lt;T&gt; where T : Entity { private readonly IPredicateBuilderFactory _builderFactory; private IPredicateBuilder&lt;T&gt; _predicateBuilder; protected SpecificationBase(IPredicateBuilderFactory builderFactory) { _builderFactory = builderFactory; } public IPredicateBuilder&lt;T&gt; PredicateBuilder { get { return _predicateBuilder ?? (_predicateBuilder = BuildPredicate()); } } protected abstract void AddSatisfactionCriterion(IPredicateBuilder&lt;T&gt; predicateBuilder); private IPredicateBuilder&lt;T&gt; BuildPredicate() { var predicateBuilder = _builderFactory.Make&lt;T&gt;(); predicateBuilder.Check(candidate =&gt; !candidate.IsDeleted) AddSatisfactionCriterion(predicateBuilder); return predicateBuilder; } } </code></pre> <p>The IPredicateBuilder is a wrapper to the predicate builder included in the <a href="http://www.albahari.com/nutshell/linqkit.aspx" rel="nofollow">LINQKit.dll</a>.</p> <p>The specification base class is responsible to create the predicate builder. Once created the criteria that should be applied to all query can be added. The predicate builder can then be passed to the inherited specifications for adding further criteria. For example:</p> <pre><code>public class IdSpecification&lt;T&gt; : SpecificationBase&lt;T&gt; where T : Entity { private readonly int _id; public IdSpecification(int id, IPredicateBuilderFactory builderFactory) : base(builderFactory) { _id = id; } protected override void AddSatisfactionCriterion(IPredicateBuilder&lt;T&gt; predicateBuilder) { predicateBuilder.And(entity =&gt; entity.Id == _id); } } </code></pre> <p>The IdSpecification's full predicate would then be:</p> <pre><code>entity =&gt; !entity.IsDeleted &amp;&amp; entity.Id == _id </code></pre> <p>The specification can then be passed to the repository which uses the <code>PredicateBuilder</code> property to build up the where clause:</p> <pre><code> public IQueryable&lt;T&gt; FindAll(ISpecification&lt;T&gt; spec) { return context.AsExpandable().Where(spec.PredicateBuilder.Complete()).AsQueryable(); } </code></pre> <p><code>AsExpandable()</code> is part of the LINQKit.dll.</p> <p>In regards to including/lazy loading properties one can extend the specification with a further property about includes. The specification base can add the base includes and then child specifications add their includes. The repository can then before fetching from the db apply the includes from the specification. </p> <pre><code> public IQueryable&lt;T&gt; Apply&lt;T&gt;(IDbSet&lt;T&gt; context, ISpecification&lt;T&gt; specification) { if (specification.IncludePaths == null) return context; return specification.IncludePaths.Aggregate&lt;string, IQueryable&lt;T&gt;&gt;(context, (current, path) =&gt; current.Include(path)); } </code></pre> <p>Let me know if something is unclear. I tried not to make this a monster post so some details might be left out. </p> <p>Edit: I realized that I didn't fully answer your question(s); navigation properties. What if you make the navigation property internal (using <a href="http://weblogs.asp.net/ricardoperes/archive/2013/08/22/mapping-non-public-members-with-entity-framework-code-first.aspx" rel="nofollow">this post to configure it</a> and creating non-mapped public properties that are IQueryable. The non mapped properties can have a custom attribute and the repository adds the base specification's predicate to the where, without eagerly loading it. When someone do apply an eager operation the filter will apply. Something like:</p> <pre><code> public T Find(int id) { var entity = Context.SingleOrDefault(x =&gt; x.Id == id); if (entity != null) { foreach(var property in entity.GetType() .GetProperties() .Where(info =&gt; info.CustomAttributes.OfType&lt;FilteredNavigationProperty&gt;().Any())) { var collection = (property.GetValue(property) as IQueryable&lt;IEntity&gt;); collection = collection.Where(spec.PredicateBuilder.Complete()); } } return entity; } </code></pre> <p>I haven't tested the above code but it could work with some tweaking :) </p> <p>Edit 2: Deletes.</p> <p>If you're using a general/generic repository you could simply add some further functionality to the delete method:</p> <pre><code> public void Delete(T entity) { var castedEntity = entity as Entity; if (castedEntity != null) { castedEntity.IsDeleted = true; } else { _context.Remove(entity); } } </code></pre>
 

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