リポジトリは、データアクセスレイヤーとドメインレイヤーの間の仲介です。
ドメインオブジェクトのメモリ内コレクションとして機能します。 クライアントは宣言型を作成します
リクエストの説明とそれらを実行のためにリポジトリに渡します。
-マーティン・ファウラーによる無料翻訳
EntityFraemwork      Repository: DbSet<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      UnitOfWork: DbContext
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    .     ,             EntityFraemwork.
:
- Generic Repository ORM.
 - Repository ( DAO).
 
.
Generic Repository
: " ORM?". : " Generic Repository, ".
, , API ORM , " " API .
:
public interface IRepository<T>
{
    T Get(int id);
    void Add(T entity);
    void Remove(T entity);
    IEnumerable<T> GetAll();
    // + -   
    IEnumerable<T> Filter(ICriteria<T> criteria);
    void Load(T entity, Expression<Func<T, TProperty>> property);
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      ORM, .
– ! , - ORM? ORM -, - , ORM " ".
, , .
, Generic Repository :
- .
 - .
 - ORM.
 - ORM.
 - .
 - , ORM.
 - ORM . !
 - , .
 
, . ORM, . ASP.NET Boilerplate. Repository .
     .  IDbSet<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
         CRUD .       (  )     IQueryable<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    .
Repository
        — Data Access Object.          .     CRUD-  : GetByLogin()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    , GetByName()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    , etc.
, . . . , , . . .
, , Data Transfer Object, . , ? , , -, SRP.
, DAO :
- .
 
  EF Core       in-memory DbContext
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    .
- .
 
, DAO:
public interface IPostsRepository
{
    IEnumerable<Post> FilterByDate(DateTime date);
    IEnumerable<Post> FilterByTag(string tag);
    IEnumerable<Post> FilterByDateAndTag(DateTime date, string tag);
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
         FilterByDateAndTag()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
           .
?
Query Builder Specification.
.NET     Query Builder: IQueryable<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
       - LINQ.
. , 80%
-     id: 
context.Entities.Find(id)
, -     :
      
context.Entities.Where(e => e.Property == value)
. 
20% -. -.
           extension-  IQueryable<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    .    —  .
Specification
- , . , .
:
public interface ISpecification<T>
{
    bool IsSatisfiedBy(T entity);
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
            IQueryable<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    .
 LINQ to Entities     Expression<Func<T, bool>>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    .             LINQ to Objects.
   .   ToExpression()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    :
public interface ISpecification<T>
{
    bool IsSatisfiedBy(T entity);
    Expression<Func<T, bool>> ToExpression();
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
         Specificaiton<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    :
public class Specification<T> : ISpecification<T>
{
    private Func<T, bool> _function;
    private Func<T, bool> Function => _function
        ?? (_function = Predicate.Compile());
    protected Expression<Func<T, bool>> Predicate;
    protected Specification() { }
    public Specification(Expression<Func<T, bool>> predicate)
    {
        Predicate = predicate;
    }
    public bool IsSatisfiedBy(T entity)
    {
        return Function.Invoke(entity);
    }
    public Expression<Func<T, bool>> ToExpression()
    {
        return Predicate;
    }
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      &&, || !. . C# Language Specification [7.11.2], : true, false, & |, && &, || |.
public static implicit operator Func<T, bool>(Specification<T> spec)
{
    return spec.Function;
}
public static implicit operator Expression<Func<T, bool>>(Specification<T> spec)
{
    return spec.Predicate;
}
public static bool operator true(Specification<T> spec)
{
    return false;
}
public static bool operator false(Specification<T> spec)
{
    return false;
}
public static Specification<T> operator !(Specification<T> spec)
{
    return new Specification<T>(
        Expression.Lambda<Func<T, bool>>(
            Expression.Not(spec.Predicate.Body),
            spec.Predicate.Parameters));
}
public static Specification<T> operator &(Specification<T> left, Specification<T> right)
{
    var leftExpr = left.Predicate;
    var rightExpr = right.Predicate;
    var leftParam = leftExpr.Parameters[0];
    var rightParam = rightExpr.Parameters[0];
    return new Specification<T>(
        Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(
                leftExpr.Body,
                new ParameterReplacer(rightParam, leftParam).Visit(rightExpr.Body)),
            leftParam));
}
public static Specification<T> operator |(Specification<T> left, Specification<T> right)
{
    var leftExpr = left.Predicate;
    var rightExpr = right.Predicate;
    var leftParam = leftExpr.Parameters[0];
    var rightParam = rightExpr.Parameters[0];
    return new Specification<T>(
        Expression.Lambda<Func<T, bool>>(
            Expression.OrElse(
                leftExpr.Body,
                new ParameterReplacer(rightParam, leftParam).Visit(rightExpr.Body)),
            leftParam));
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
               ExpressionVisitor
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    :
internal class ParameterReplacer : ExpressionVisitor
{
    readonly ParameterExpression _parameter;
    readonly ParameterExpression _replacement;
    public ParameterReplacer(ParameterExpression parameter, ParameterExpression replacement)
    {
        _parameter = parameter;
        _replacement = replacement;
    }
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return base.VisitParameter(_parameter == node ? _replacement : node);
    }
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      Specification<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      Expresison
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
         :
public class SpecificationExpander : ExpressionVisitor
{
    protected override Expression VisitUnary(UnaryExpression node)
    {
        if (node.NodeType == ExpressionType.Convert)
        {
            MethodInfo method = node.Method;
            if (method != null && method.Name == "op_Implicit")
            {
                Type declaringType = method.DeclaringType;
                if (declaringType.GetTypeInfo().IsGenericType
                    && declaringType.GetGenericTypeDefinition() == typeof(Specification<>))
                {
                    const string name = nameof(Specification<object>.ToExpression);
                    MethodInfo toExpression = declaringType.GetMethod(name);
                    return ExpandSpecification(node.Operand, toExpression);
                }
            }
        }
        return base.VisitUnary(node);
    }
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        MethodInfo method = node.Method;
        if (method.Name == nameof(ISpecification<object>.ToExpression))
        {
            Type declaringType = method.DeclaringType;
            Type[] interfaces = declaringType.GetTypeInfo().GetInterfaces();
            if (interfaces.Any(i => i.GetTypeInfo().IsGenericType
                && i.GetGenericTypeDefinition() == typeof(ISpecification<>)))
            {
                return ExpandSpecification(node.Object, method);
            }
        }
        return base.VisitMethodCall(node);
    }
    private Expression ExpandSpecification(Expression instance, MethodInfo toExpression)
    {
        return Visit((Expression)GetValue(Expression.Call(instance, toExpression)));
    }
    // http://stackoverflow.com/a/2616980/1402923
    private static object GetValue(Expression expression)
    {
        var objectMember = Expression.Convert(expression, typeof(object));
        var getterLambda = Expression.Lambda<Func<object>>(objectMember);
        return getterLambda.Compile().Invoke();
    }
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    — :
public class UserIsActiveSpec : Specification<User>
{
    public UserIsActiveSpec()
    {
        Predicate = u => u.IsActive;
    }
}
var spec = new UserIsActiveSpec();
var user = new User { IsActive = true };
Assert.IsTrue(spec.IsSatisfiedBy(user));
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      — :
public class UserByLoginSpec : Specification<User>
{
    public UserByLoginSpec(string login)
    {
        Predicate = u => u.Login == login;
    }
}
public class UserCombinedSpec : Specification<User>
{
    public UserCombinedSpec(string login)
        : base(new UserIsActive() && new UserByLogin(login))
    {
    }
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      — LINQ to Entities:
var spec = new UserByLoginSpec("admin");
context.Users.Where(spec.ToExpression());
//    (  implicit conversion  Expression)
context.Uses.Where(new UserByLoginSpec("admin") || new UserByLoginSpec("user"));
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      , Specification ASP.NET Boilerplate. PredicateBuilder LinqKit.
IQueryable
    -  IQueryable<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    . :
public static IQueryable<Post> FilterByAuthor(
    this IQueryable<Post> posts, int authorId)
{
    return posts.Where(p => p.AuthorId = authorId);
}
public static IQueryable<Comment> FilterTodayComments(
    this IQueryable<Comment> comments)
{
    DateTime today = DateTime.Now.Date;
    return comments.Where(c => c.CreationTime > today)
}
Comment[] comments = context.Posts
    .FilterByAuthor(authorId)    // it's OK
    .SelectMany(p => p.Comments
        .AsQueryable()
        .FilterTodayComments())  // will throw Exception
    .ToArray();
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
         ,    extension-   ,      Exception
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    .      Expression Tree   SelectMany()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    .  LINQ to Entities    .
. :
ExpressionVisitor
, extension-.-   
IQueryable<T>
,ExpressionVisitor
. -   
AsExpandable()
,IQueryable<T>
. -  
[Expandable]
, extension- .
Where()
Select()
extension-, . 
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ExpandableAttribute : Attribute { }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      public static IQueryable<T> AsExpandable<T>(this IQueryable<T> queryable)
{
    return queryable.AsVisitable(new ExtensionExpander());
}
public static IQueryable<T> AsVisitable<T>(
    this IQueryable<T> queryable, params ExpressionVisitor[] visitors)
{
    return queryable as VisitableQuery<T>
        ?? VisitableQueryFactory<T>.Create(queryable, visitors);
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
         IQueryable<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      IQueryProvider
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    :
public interface IQueryable<T>
{
    IEnumerator GetEnumerator();     // from IEnumerable
    IEnumerator<T> GetEnumerator();  // from IEnumerable<T>
    Type ElementType { get; }        // from IQueryable
    Expression Expression { get; }   // from IQueryable
    IQueryProvider Provider { get; } // from IQueryable
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      internal class VisitableQuery<T> : IQueryable<T>, IOrderedQueryable<T>, IOrderedQueryable
{
    readonly ExpressionVisitor[] _visitors;
    readonly IQueryable<T> _queryable;
    readonly VisitableQueryProvider<T> _provider;
    internal ExpressionVisitor[] Visitors => _visitors;
    internal IQueryable<T> InnerQuery => _queryable;
    public VisitableQuery(IQueryable<T> queryable, params ExpressionVisitor[] visitors)
    {
        _queryable = queryable;
        _visitors = visitors;
        _provider = new VisitableQueryProvider<T>(this);
    }
    Expression IQueryable.Expression => _queryable.Expression;
    Type IQueryable.ElementType => typeof(T);
    IQueryProvider IQueryable.Provider => _provider;
    public IEnumerator<T> GetEnumerator()
    {
        return _queryable.GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return _queryable.GetEnumerator();
    }
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    public interface IQueryProvider
{
    IQueryable CreateQuery(Expression expression);
    IQueryable<TElement> CreateQuery<TElement>(Expression expression);
    object Execute(Expression expression);
    TResult Execute<TResult>(Expression expression);
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      internal class VisitableQueryProvider<T> : IQueryProvider
{
    readonly VisitableQuery<T> _query;
    public VisitableQueryProvider(VisitableQuery<T> query)
    {
        _query = query;
    }
    IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(Expression expression)
    {
        expression = _query.Visitors.Visit(expression);
        return _query.InnerQuery.Provider
            .CreateQuery<TElement>(expression)
            .AsVisitable(_query.Visitors);
    }
    IQueryable IQueryProvider.CreateQuery(Expression expression)
    {
        expression = _query.Visitors.Visit(expression);
        return _query.InnerQuery.Provider.CreateQuery(expression);
    }
    TResult IQueryProvider.Execute<TResult>(Expression expression)
    {
        expression = _query.Visitors.Visit(expression);
        return _query.InnerQuery.Provider.Execute<TResult>(expression);
    }
    object IQueryProvider.Execute(Expression expression)
    {
        expression = _query.Visitors.Visit(expression);
        return _query.InnerQuery.Provider.Execute(expression);
    }
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    internal static class VisitorExtensions
{
    public static Expression Visit(this ExpressionVisitor[] visitors, Expression node)
    {
        if (visitors != null)
        {
            foreach (var visitor in visitors)
            {
                node = visitor.Visit(node);
            }
        }
        return node;
    }
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
         .    ,   ToListAsync()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    , EntityFramework  EF Core   : IDbAsyncEnumerable<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      IAsyncEnumerable<T>
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    .     .     ExpandableQuery  LinqKit,     ExpressionVisitor
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    .
, , ExpressionVisitor:
public class ExtensionExpander : ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        MethodInfo method = node.Method;
        if (method.IsDefined(typeof(ExtensionAttribute), true)
            && method.IsDefined(typeof(ExpandableAttribute), true))
        {
            ParameterInfo[] methodParams = method.GetParameters();
            Type queryableType = methodParams.First().ParameterType;
            Type entityType = queryableType.GetGenericArguments().Single();
            object inputQueryable = MakeEnumerableQuery(entityType);
            object[] arguments = new object[methodParams.Length];
            arguments[0] = inputQueryable;
            var argumentReplacements = new List<KeyValuePair<string, Expression>>();
            for (int i = 1; i < methodParams.Length; i++)
            {
                try
                {
                    arguments[i] = GetValue(node.Arguments[i]);
                }
                catch (InvalidOperationException)
                {
                    ParameterInfo paramInfo = methodParams[i];
                    Type paramType = paramInfo.GetType();
                    arguments[i] = paramType.GetTypeInfo().IsValueType
                        ? Activator.CreateInstance(paramType) : null;
                    argumentReplacements.Add(
                        new KeyValuePair<string, Expression>(paramInfo.Name, node.Arguments[i]));
                }
            }
            object outputQueryable = method.Invoke(null, arguments);
            Expression expression = ((IQueryable)outputQueryable).Expression;
            Expression realQueryable = node.Arguments[0];
            if (!typeof(IQueryable).IsAssignableFrom(realQueryable.Type))
            {
                MethodInfo asQueryable = _asQueryable.MakeGenericMethod(entityType);
                realQueryable = Expression.Call(asQueryable, realQueryable);
            }
            expression = new ExtensionRebinder(
                inputQueryable, realQueryable, argumentReplacements).Visit(expression);
            return Visit(expression);
        }
        return base.VisitMethodCall(node);
    }
    private static object MakeEnumerableQuery(Type entityType)
    {
        return _queryableEmpty.MakeGenericMethod(entityType).Invoke(null, null);
    }
    private static readonly MethodInfo _asQueryable = typeof(Queryable)
        .GetMethods(BindingFlags.Static | BindingFlags.Public)
        .First(m => m.Name == nameof(Queryable.AsQueryable) && m.IsGenericMethod);
    private static readonly MethodInfo _queryableEmpty = (typeof(ExtensionExpander))
        .GetMethod(nameof(QueryableEmpty), BindingFlags.Static | BindingFlags.NonPublic);
    private static IQueryable<T> QueryableEmpty<T>()
    {
        return Enumerable.Empty<T>().AsQueryable();
    }
    // http://stackoverflow.com/a/2616980/1402923
    private static object GetValue(Expression expression)
    {
        var objectMember = Expression.Convert(expression, typeof(object));
        var getterLambda = Expression.Lambda<Func<object>>(objectMember);
        return getterLambda.Compile().Invoke();
    }
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    internal class ExtensionRebinder : ExpressionVisitor
{
    readonly object _originalQueryable;
    readonly Expression _replacementQueryable;
    readonly List<KeyValuePair<string, Expression>> _argumentReplacements;
    public ExtensionRebinder(
        object originalQueryable, Expression replacementQueryable,
        List<KeyValuePair<string, Expression>> argumentReplacements)
    {
        _originalQueryable = originalQueryable;
        _replacementQueryable = replacementQueryable;
        _argumentReplacements = argumentReplacements;
    }
    protected override Expression VisitConstant(ConstantExpression node)
    {
        return node.Value == _originalQueryable ? _replacementQueryable : node;
    }
    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.NodeType == ExpressionType.MemberAccess
            && node.Expression.NodeType == ExpressionType.Constant
            && node.Expression.Type.GetTypeInfo().IsDefined(typeof(CompilerGeneratedAttribute)))
        {
            string argumentName = node.Member.Name;
            Expression replacement = _argumentReplacements
                .Where(p => p.Key == argumentName)
                .Select(p => p.Value)
                .FirstOrDefault();
            if (replacement != null)
            {
                return replacement;
            }
        }
        return base.VisitMember(node);
    }
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    — extension- Expression Tree:
[Expandable]
public static IQueryable<Post> FilterByAuthor(
    this IEnumerable<Post> posts, int authorId)
{
    return posts.AsQueryable().Where(p => p.AuthorId = authorId);
}
[Expandable]
public static IQueryable<Comment> FilterTodayComments(
    this IEnumerable<Comment> comments)
{
    DateTime today = DateTime.Now.Date;
    return comments.AsQueryable().Where(c => c.CreationTime > today)
}
Comment[] comments = context.Posts
    .AsExpandable()
    .FilterByAuthor(authorId)    // it's OK
    .SelectMany(p => p.Comments
        .FilterTodayComments())  // it's OK too
    .ToArray();
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      TL; DR
, ! , . ?
GitHub: EntityFramework.CommonTools, NuGet:
PM> Install-Package EntityFramework.CommonTools
PM> Install-Package EntityFrameworkCore.CommonTools
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      Update
public class DatabaseQueryBenchmark
{
    readonly DbConnection _connection = Context.CreateConnection();
    [Benchmark(Baseline = true)]
    public object RawQuery()
    {
        using (var context = new Context(_connection))
        {
            DateTime today = DateTime.Now.Date;
            return context.Users
                .Where(u => u.Posts.Any(p => p.Date > today))
                .FirstOrDefault();
        }
    }
    [Benchmark]
    public object ExpandableQuery()
    {
        using (var context = new Context(_connection))
        {
            return context.Users
                .AsExpandable()
                .Where(u => u.Posts.FilterToday().Any())
                .ToList();
        }
    }
    readonly Random _random = new Random();
    [Benchmark]
    public object NotCachedQuery()
    {
        using (var context = new Context(_connection))
        {
            int[] postIds = new[] { _random.Next(), _random.Next() };
            return context.Users
                .Where(u => u.Posts.Any(p => postIds.Contains(p.Id)))
                .ToList();
        }
    }
}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
              Method |        Median |     StdDev | Scaled | Scaled-SD |
---------------- |-------------- |----------- |------- |---------- |
        RawQuery |   555.6202 μs | 15.1837 μs |   1.00 |      0.00 |
 ExpandableQuery |   644.6258 μs |  3.7793 μs |   1.15 |      0.03 | <<<
  NotCachedQuery | 2,277.7138 μs | 10.9754 μs |   4.06 |      0.10 |
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    , . 15-30 %.
- https://martinfowler.com/eaaCatalog/repository.html
 - https://softwareengineering.stackexchange.com/questions/180851/why-shouldnt-i-use-the-repository-pattern-with-entity-framework
 - http://enterprisecraftsmanship.com/2016/02/08/specification-pattern-c-implementation
 - https://aspnetboilerplate.com/Pages/Documents/Repositories
 - https://aspnetboilerplate.com/Pages/Documents/Specifications
 - http://www.albahari.com/nutshell/linqkit.aspx
 - https://msdn.microsoft.com/en-us/library/aa691312(v=vs.71).aspx