ardalis / Specification

Base class with tests for adding specifications to a DDD model

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support IQueryable<T>.AsTracking<T> methods

codelovercc opened this issue · comments

I've noticed that ISpecificationBuilder<T> has AsNoTracking<T> method.

Situation one:

In my abstract repository class, it has a method:

   protected virtual IQueryable<TEntity> ApplySpecification(ISpecification<TEntity> specification
        bool evaluateCriteriaOnly = false)
    {
        return SpecificationEvaluator.GetQuery(EntitySet.AsNoTracking(), specification, evaluateCriteriaOnly);
    }

EntitySet is an instance of DbSet<TEntity>. EntitySet.AsNoTracking() makes that the query results are not tracked by default.

Now I want some Specification to make sure its query results are being tracked. How do I do in my Specification class?

Since the ISpecificationBuilder<T>.AsNoTracking<T> is supported, I think we should support ISpecificationBuilder<T>.AsTracking<T> just like IQueryable<T>.AsTracking<T> do.

Situation two:

Let the specifications control tracking.
I have a pagination specification class:

public abstract class PaginationSpecification<T> : Specification<T>, ISearchSpecification<T> where T : class
{
    public sealed override ISpecificationBuilder<T> Query => null!;

    protected PaginationSpecification(IPagination pagination)
        : this(pagination, InMemorySpecificationEvaluator.Default, SpecificationValidator.Default)
    {
    }

    protected PaginationSpecification(IPagination pagination,
        IInMemorySpecificationEvaluator inMemorySpecificationEvaluator)
        : this(pagination, inMemorySpecificationEvaluator, SpecificationValidator.Default)
    {
    }

    protected PaginationSpecification(IPagination pagination, ISpecificationValidator specificationValidator)
        : this(pagination, InMemorySpecificationEvaluator.Default, specificationValidator)
    {
    }

    protected PaginationSpecification(IPagination pagination,
        IInMemorySpecificationEvaluator inMemorySpecificationEvaluator,
        ISpecificationValidator specificationValidator, bool isTracking = false) : base(inMemorySpecificationEvaluator, specificationValidator)
    {
        if (pagination is IOrderRequestModel orderRequestModel)
        {
            OrderedSpecificationAdapt.Instance.ApplyOrder(Query, orderRequestModel);
        }

        Query.AsNoTracking().Skip(pagination.PageSize * (pagination.PageIndex - 1))
            .Take(pagination.PageSize);
    }

    public bool IsPredicateLess() => !WhereExpressions.Any();
}

Query.AsNoTracking() makes class PaginationSpecification<T> is an untracked specification.

Now, I create a TrackingPaginationSpecification<T> class that inherit from PaginationSpecification<T>, so I can use TrackingPaginationSpecification<T> to query pagination results and edit them paged.

public abstract class TrackingPaginationSpecification<T> : PaginationSpecification<T>
{ 
    protected PaginationSpecification(IPagination pagination)
    {
        // make the query is tracked
        // this will cause compile error,  AsTracking() method does not exists.
        Query.AsTracking();
    }
}

What can we do in this situation?

We can support AsTracking like this:

In static class SpecificationBuilderExtensions , add two methods

    /// <summary>
    /// If the entity instances are modified, this will be detected
    /// by the change tracker.
    /// </summary>
    /// <param name="specificationBuilder"></param>
    public static ISpecificationBuilder<T> AsTracking<T>(
        this ISpecificationBuilder<T> specificationBuilder) where T : class
        => AsTracking(specificationBuilder, true);

    /// <summary>
    /// If the entity instances are modified, this will be detected
    /// by the change tracker.
    /// </summary>
    /// <param name="specificationBuilder"></param>
    /// <param name="condition">If false, the setting will be discarded.</param>
    public static ISpecificationBuilder<T> AsTracking<T>(
        this ISpecificationBuilder<T> specificationBuilder,
        bool condition) where T : class
    {
      if (condition)
      {
        specificationBuilder.Specification.AsNoTracking = false;
      }

      return specificationBuilder;
    }

In class AsNoTrackingEvaluator, modify GetQuery<T> method.

    public IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specification) where T : class
    {
      return specification.AsNoTracking ?  query.AsNoTracking() : query.AsTracking();
    }

In situation one, We can let the repository class control the query is tracked or not and ignore specification's AsNoTracking.

protected virtual IQueryable<TEntity> ApplySpecification(ISpecification<TEntity> specification,
        bool isTracked = true,
        bool evaluateCriteriaOnly = false) where TEntity : class
    {
        var q = SpecificationEvaluator.GetQuery(EntitySet, specification, evaluateCriteriaOnly);
        return isTracked ? q.AsTracking() : q.AsNoTracking();
    }

So, What do we say? I still think that support AsTracking will be nicer :)

@fiseni thoughts?

I think we had another request for AsTracking, so let's add it. @codelovercc Do you want to create a PR?

Sure, I'd like to.

Should we rename class AsNoTrackingEvaluator to TrackingEvaluator? When it support AsTracking and AsNoTracking, this may be more suitable but it's a breaking change.

No, no need for that. You'll create a separate new file called AsTrackingEvaluator.

Completed, PR #338