
リポジトリは、データアクセスレイヤーとドメインレイヤーの間の仲介です。
ドメインオブジェクトのメモリ内コレクションとして機能します。 クライアントは宣言型を作成します
リクエストの説明とそれらを実行のためにリポジトリに渡します。
-マーティン・ファウラーによる無料翻訳
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