diff --git a/src/Stove.Dapper/Dapper/DapperRepositoryRegistrar.cs b/src/Stove.Dapper/Dapper/DapperRepositoryRegistrar.cs index 32ad30f..50ac4fb 100644 --- a/src/Stove.Dapper/Dapper/DapperRepositoryRegistrar.cs +++ b/src/Stove.Dapper/Dapper/DapperRepositoryRegistrar.cs @@ -28,7 +28,7 @@ public static void RegisterRepositories(Type dbContextType, IIocBuilder builder) ? autoRepositoryAttr.RepositoryImplementation.MakeGenericType(entityTypeInfo.EntityType) : autoRepositoryAttr.RepositoryImplementation.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType); - builder.RegisterServices(r => r.UseBuilder(cb => cb.RegisterType(implType).As(genericRepositoryType).AsImplementedInterfaces())); + builder.RegisterServices(r => r.UseBuilder(cb => cb.RegisterType(implType).As(genericRepositoryType).AsImplementedInterfaces().InjectPropertiesAsAutowired())); } else { @@ -38,7 +38,7 @@ public static void RegisterRepositories(Type dbContextType, IIocBuilder builder) ? autoRepositoryAttr.RepositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType) : autoRepositoryAttr.RepositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType, primaryKeyType); - builder.RegisterServices(r => r.UseBuilder(cb => cb.RegisterType(implType).As(genericRepositoryTypeWithPrimaryKey).AsImplementedInterfaces())); + builder.RegisterServices(r => r.UseBuilder(cb => cb.RegisterType(implType).As(genericRepositoryTypeWithPrimaryKey).AsImplementedInterfaces().InjectPropertiesAsAutowired())); } } } diff --git a/src/Stove.Dapper/Dapper/Expressions/DapperEvaluator.cs b/src/Stove.Dapper/Dapper/Expressions/DapperEvaluator.cs new file mode 100644 index 0000000..881923c --- /dev/null +++ b/src/Stove.Dapper/Dapper/Expressions/DapperEvaluator.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Stove.Dapper.Dapper.Expressions +{ + /// + /// Reference + /// From:http://blogs.msdn.com/b/mattwar/archive/2007/08/01/linq-building-an-iqueryable-provider-part-iii.aspx + /// + internal class Evaluator + { + public static Expression PartialEval(Expression exp, Func canBeEval) + { + return new SubtreeEvaluator(new Nominator(canBeEval).Nominate(exp)).Eval(exp); + } + + public static Expression PartialEval(Expression exp) + { + return PartialEval(exp, CanBeEvaluatedLocally); + } + + private static bool CanBeEvaluatedLocally(Expression exp) + { + return exp.NodeType != ExpressionType.Parameter; + } + + private class SubtreeEvaluator : ExpressionVisitor + { + private readonly HashSet _candidates; + + internal SubtreeEvaluator(HashSet candidates) + { + _candidates = candidates; + } + + internal Expression Eval(Expression exp) + { + return Visit(exp); + } + + public override Expression Visit(Expression exp) + { + if (exp == null) + { + return null; + } + + if (_candidates.Contains(exp)) + { + return Evaluate(exp); + } + + return base.Visit(exp); + } + + private Expression Evaluate(Expression exp) + { + if (exp.NodeType == ExpressionType.Constant) + { + return exp; + } + + LambdaExpression lambda = Expression.Lambda(exp); + Delegate del = lambda.Compile(); + + return Expression.Constant(del.DynamicInvoke(null), exp.Type); + } + } + + private class Nominator : ExpressionVisitor + { + private readonly Func _canBeEval; + private HashSet _candidates; + private bool _cannotBeEval; + + internal Nominator(Func canBeEval) + { + _canBeEval = canBeEval; + } + + internal HashSet Nominate(Expression exp) + { + _candidates = new HashSet(); + Visit(exp); + return _candidates; + } + + public override Expression Visit(Expression exp) + { + if (exp == null) + { + return null; + } + + bool saveCannotBeEval = _cannotBeEval; + _cannotBeEval = false; + + base.Visit(exp); + + if (!_cannotBeEval) + { + if (_canBeEval(exp)) + { + _candidates.Add(exp); + } + else + { + _cannotBeEval = true; + } + } + + _cannotBeEval |= saveCannotBeEval; + + return exp; + } + } + } +} diff --git a/src/Stove.Dapper/Dapper/Expressions/DapperExpressionExtensions.cs b/src/Stove.Dapper/Dapper/Expressions/DapperExpressionExtensions.cs new file mode 100644 index 0000000..7ad3221 --- /dev/null +++ b/src/Stove.Dapper/Dapper/Expressions/DapperExpressionExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq.Expressions; + +using DapperExtensions; + +using JetBrains.Annotations; + +using Stove.Domain.Entities; + +namespace Stove.Dapper.Dapper.Expressions +{ + /// + /// http://stackoverflow.com/questions/15154783/pulling-apart-expressionfunct-object + /// http://stackoverflow.com/questions/16083895/grouping-lambda-expressions-by-operators-and-using-them-with-dapperextensions-p + /// http://blogs.msdn.com/b/mattwar/archive/2007/07/31/linq-building-an-iqueryable-provider-part-ii.aspx + /// http://msdn.microsoft.com/en-us/library/bb546136(v=vs.110).aspx + /// http://stackoverflow.com/questions/14437239/change-a-linq-expression-predicate-from-one-type-to-another/14439071#14439071 + /// + internal static class DapperExpressionExtensions + { + public static IPredicate ToPredicateGroup([NotNull] this Expression> expression) where TEntity : class, IEntity + { + Check.NotNull(expression, nameof(expression)); + + var dev = new DapperExpressionVisitor(); + IPredicate pg = dev.Process(expression); + + return pg; + } + } +} diff --git a/src/Stove.Dapper/Dapper/Expressions/DapperExpressionVisitor.cs b/src/Stove.Dapper/Dapper/Expressions/DapperExpressionVisitor.cs new file mode 100644 index 0000000..5033689 --- /dev/null +++ b/src/Stove.Dapper/Dapper/Expressions/DapperExpressionVisitor.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +using DapperExtensions; + +using Stove.Domain.Entities; + +namespace Stove.Dapper.Dapper.Expressions +{ + /// + /// This class converts an Expression{Func{TEntity, bool}} into an IPredicate group that can be used with + /// DapperExtension's predicate system + /// + /// The type of the entity. + /// The type of the primary key. + /// + internal class DapperExpressionVisitor : ExpressionVisitor where TEntity : class, IEntity + { + private PredicateGroup _pg; + private Expression _processedProperty; + private bool _unarySpecified; + + public DapperExpressionVisitor() + { + Expressions = new HashSet(); + } + + /// + /// Holds BinaryExpressions + /// + public HashSet Expressions { get; } + + public IPredicate Process(Expression exp) + { + _pg = new PredicateGroup { Predicates = new List() }; + Visit(Evaluator.PartialEval(exp)); + + // the 1st expression determines root group operator + if (Expressions.Any()) + { + _pg.Operator = Expressions.First().NodeType == ExpressionType.OrElse ? GroupOperator.Or : GroupOperator.And; + } + + return _pg.Predicates.Count == 1 ? _pg.Predicates[0] : _pg; + } + + private static PredicateGroup GetLastPredicateGroup(PredicateGroup grp) + { + IList groups = grp.Predicates; + + if (!groups.Any()) + { + return grp; + } + + IPredicate last = groups.Last(); + + if (last is PredicateGroup) + { + return GetLastPredicateGroup(last as PredicateGroup); + } + + return grp; + } + + private IFieldPredicate GetLastField() + { + PredicateGroup lastGrp = GetLastPredicateGroup(_pg); + + IPredicate last = lastGrp.Predicates.Last(); + + return last as IFieldPredicate; + } + + private static Operator DetermineOperator(Expression binaryExpression) + { + switch (binaryExpression.NodeType) + { + case ExpressionType.Equal: + return Operator.Eq; + case ExpressionType.GreaterThan: + return Operator.Gt; + case ExpressionType.GreaterThanOrEqual: + return Operator.Ge; + case ExpressionType.LessThan: + return Operator.Lt; + case ExpressionType.LessThanOrEqual: + return Operator.Le; + default: + return Operator.Eq; + } + } + + private void AddField(MemberExpression exp, Operator op = Operator.Eq, object value = null, bool not = false) + { + PredicateGroup pg = GetLastPredicateGroup(_pg); + + // need convert from Expression> to Expression> as this is what Predicates.Field() requires + Expression> fieldExp = Expression.Lambda>(Expression.Convert(exp, typeof(object)), exp.Expression as ParameterExpression); + + IFieldPredicate field = Predicates.Field(fieldExp, op, value, not); + pg.Predicates.Add(field); + } + + protected override Expression VisitBinary(BinaryExpression node) + { + Expressions.Add(node); + + ExpressionType nt = node.NodeType; + + if (nt == ExpressionType.OrElse || nt == ExpressionType.AndAlso) + { + var pg = new PredicateGroup + { + Predicates = new List(), + Operator = nt == ExpressionType.OrElse ? GroupOperator.Or : GroupOperator.And + }; + + _pg.Predicates.Add(pg); + } + + Visit(node.Left); + + if (node.Left is MemberExpression) + { + IFieldPredicate field = GetLastField(); + field.Operator = DetermineOperator(node); + + if (nt == ExpressionType.NotEqual) + { + field.Not = true; + } + } + + Visit(node.Right); + + return node; + } + + protected override Expression VisitMember(MemberExpression node) + { + if (node.Member.MemberType != MemberTypes.Property || node.Expression.Type != typeof(TEntity)) + { + throw new NotSupportedException($"The member '{node}' is not supported"); + } + + // skip if prop is part of a VisitMethodCall + if (_processedProperty != null && _processedProperty == node) + { + _processedProperty = null; + return node; + } + + AddField(node); + + return node; + } + + protected override Expression VisitConstant(ConstantExpression node) + { + IFieldPredicate field = GetLastField(); + field.Value = node.Value; + + return node; + } + + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (node.Type == typeof(bool) && node.Method.DeclaringType == typeof(string)) + { + object arg = ((ConstantExpression)node.Arguments[0]).Value; + var op = Operator.Like; + + switch (node.Method.Name.ToLowerInvariant()) + { + case "startswith": + arg = arg + "%"; + break; + case "endswith": + arg = "%" + arg; + break; + case "contains": + arg = "%" + arg + "%"; + break; + case "equals": + op = Operator.Eq; + break; + default: + throw new NotSupportedException($"The method '{node}' is not supported"); + } + + // this is a PropertyExpression but as it's internal, to use, we cast to the base MemberExpression instead (see http://social.msdn.microsoft.com/Forums/en-US/ab528f6a-a60e-4af6-bf31-d58e3f373356/resolving-propertyexpressions-and-fieldexpressions-in-a-custom-linq-provider) + _processedProperty = node.Object; + var me = _processedProperty as MemberExpression; + + AddField(me, op, arg, _unarySpecified); + + // reset if applicable + _unarySpecified = false; + + return node; + } + + throw new NotSupportedException($"The method '{node}' is not supported"); + } + + protected override Expression VisitUnary(UnaryExpression node) + { + if (node.NodeType != ExpressionType.Not) + { + throw new NotSupportedException($"The unary operator '{node.NodeType}' is not supported"); + } + + _unarySpecified = true; + + return base.VisitUnary(node); // returning base because we want to continue further processing - ie subsequent call to VisitMethodCall + } + } +} diff --git a/src/Stove.Dapper/Dapper/Repositories/DapperRepositoryBaseOfTEntityAndTPrimaryKey.cs b/src/Stove.Dapper/Dapper/Repositories/DapperRepositoryBaseOfTEntityAndTPrimaryKey.cs index 0cc9001..1e107fb 100644 --- a/src/Stove.Dapper/Dapper/Repositories/DapperRepositoryBaseOfTEntityAndTPrimaryKey.cs +++ b/src/Stove.Dapper/Dapper/Repositories/DapperRepositoryBaseOfTEntityAndTPrimaryKey.cs @@ -1,15 +1,19 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.Entity; +using System.Linq.Expressions; using System.Threading.Tasks; using Dapper; using DapperExtensions; +using Stove.Dapper.Dapper.Expressions; using Stove.Domain.Entities; using Stove.Domain.Repositories; +using Stove.Domain.Uow; using Stove.EntityFramework.EntityFramework; namespace Stove.Dapper.Dapper.Repositories @@ -35,6 +39,13 @@ public virtual DbConnection Connection get { return Context.Database.Connection; } } + /// + /// Gets the active transaction. If Dapper is active then should be started before + /// and there must be an active transaction. + /// + /// + /// The active transaction. + /// public virtual IDbTransaction ActiveTransaction { get { return Context.Database.CurrentTransaction.UnderlyingTransaction; } @@ -42,7 +53,12 @@ public virtual IDbTransaction ActiveTransaction public override TEntity Get(TPrimaryKey id) { - return Connection.Get(id: id, transaction: ActiveTransaction); + return Connection.Get(id, ActiveTransaction); + } + + public override Task GetAsync(TPrimaryKey id) + { + return Connection.GetAsync(id, ActiveTransaction); } public override IEnumerable GetList() @@ -52,99 +68,169 @@ public override IEnumerable GetList() public override IEnumerable GetList(object predicate) { - return Connection.GetList(predicate: predicate, transaction: ActiveTransaction); + return Connection.GetList(predicate, transaction: ActiveTransaction); } - public override IEnumerable GetListPaged( - int pageNumber, - int itemsPerPage, - string conditions, - string order, - object predicate, - string sortingProperty, - bool ascending = true) + public override Task> GetListAsync() { - return Connection.GetPage( - predicate: predicate, - sort: new List { new Sort { Ascending = ascending, PropertyName = sortingProperty } }, - page: pageNumber, - resultsPerPage: itemsPerPage, - transaction: ActiveTransaction); + return Connection.GetListAsync(transaction: ActiveTransaction); } - public override Task GetAsync(TPrimaryKey id) + public override Task> GetListAsync(object predicate) { - return Connection.GetAsync(id: id, transaction: ActiveTransaction); + return Connection.GetListAsync(predicate, transaction: ActiveTransaction); } - public override Task> GetListAsync() + public override IEnumerable GetListPaged( + object predicate, + int pageNumber, + int itemsPerPage, + string sortingProperty, + bool ascending = true) { - return Connection.GetListAsync(transaction: ActiveTransaction); + return Connection.GetPage( + predicate, + new List { new Sort { Ascending = ascending, PropertyName = sortingProperty } }, + pageNumber, + itemsPerPage, + ActiveTransaction); } - public override Task> GetListAsync(object predicate) + public override Task> GetListPagedAsync( + object predicate, + int pageNumber, + int itemsPerPage, + string sortingProperty, + bool ascending = true) { - return Connection.GetListAsync(predicate: predicate, transaction: ActiveTransaction); + return Connection.GetPageAsync( + predicate, + new List { new Sort { Ascending = ascending, PropertyName = sortingProperty } }, + pageNumber, + itemsPerPage, + ActiveTransaction); } public override int Count(object predicate) { - return Connection.Count(predicate: predicate, transaction: ActiveTransaction); + return Connection.Count(predicate, ActiveTransaction); } public override Task CountAsync(object predicate) { - return Connection.CountAsync(predicate: predicate, transaction: ActiveTransaction); + return Connection.CountAsync(predicate, ActiveTransaction); } public override IEnumerable Query(string query, object parameters) { - return Connection.Query(sql: query, param: parameters, transaction: ActiveTransaction); + return Connection.Query(query, parameters, ActiveTransaction); } public override Task> QueryAsync(string query, object parameters) { - return Connection.QueryAsync(sql: query, param: parameters, transaction: ActiveTransaction); + return Connection.QueryAsync(query, parameters, ActiveTransaction); } public override IEnumerable Query(string query) { - return Connection.Query(sql: query, transaction: ActiveTransaction); + return Connection.Query(query, transaction: ActiveTransaction); } public override Task> QueryAsync(string query) { - return Connection.QueryAsync(sql: query, transaction: ActiveTransaction); + return Connection.QueryAsync(query, transaction: ActiveTransaction); } public override IEnumerable Query(string query, object parameters) { - return Connection.Query(sql: query, param: parameters, transaction: ActiveTransaction); + return Connection.Query(query, parameters, ActiveTransaction); } public override Task> QueryAsync(string query, object parameters) { - return Connection.QueryAsync(sql: query, param: parameters, transaction: ActiveTransaction); + return Connection.QueryAsync(query, parameters, ActiveTransaction); } public override IEnumerable GetSet(object predicate, int firstResult, int maxResults, string sortingProperty, bool ascending = true) { return Connection.GetSet( - predicate: predicate, - sort: new List { new Sort { Ascending = ascending, PropertyName = sortingProperty } }, - firstResult: firstResult, - maxResults: maxResults, - transaction: ActiveTransaction); + predicate, + new List { new Sort { Ascending = ascending, PropertyName = sortingProperty } }, + firstResult, + maxResults, + ActiveTransaction); } public override Task> GetSetAsync(object predicate, int firstResult, int maxResults, string sortingProperty, bool ascending = true) { return Connection.GetSetAsync( - predicate: predicate, - sort: new List { new Sort { Ascending = ascending, PropertyName = sortingProperty } }, - firstResult: firstResult, - maxResults: maxResults, - transaction: ActiveTransaction); + predicate, + new List { new Sort { Ascending = ascending, PropertyName = sortingProperty } }, + firstResult, + maxResults, + ActiveTransaction); + } + + public override IEnumerable GetListPaged(Expression> predicate, int pageNumber, int itemsPerPage, string sortingProperty, bool ascending = true) + { + return Connection.GetPage( + predicate.ToPredicateGroup(), + new List { new Sort { Ascending = ascending, PropertyName = sortingProperty } }, + pageNumber, + itemsPerPage, + ActiveTransaction); + } + + public override int Count(Expression> predicate) + { + return Connection.Count(predicate.ToPredicateGroup(), ActiveTransaction); + } + + public override IEnumerable GetSet(Expression> predicate, int firstResult, int maxResults, string sortingProperty, bool ascending = true) + { + return Connection.GetSet( + predicate.ToPredicateGroup(), + new List { new Sort { Ascending = ascending, PropertyName = sortingProperty } }, + firstResult, + maxResults, + ActiveTransaction + ); + } + + public override IEnumerable GetList(Expression> predicate) + { + return Connection.GetList(predicate.ToPredicateGroup(), transaction: ActiveTransaction); + } + + public override Task> GetListAsync(Expression> predicate) + { + return Connection.GetListAsync(predicate.ToPredicateGroup(), transaction: ActiveTransaction); + } + + public override Task> GetSetAsync(Expression> predicate, int firstResult, int maxResults, string sortingProperty, bool ascending = true) + { + return Connection.GetSetAsync( + predicate.ToPredicateGroup(), + new List { new Sort { Ascending = ascending, PropertyName = sortingProperty } }, + firstResult, + maxResults, + ActiveTransaction + ); + } + + public override Task> GetListPagedAsync(Expression> predicate, int pageNumber, int itemsPerPage, string sortingProperty, bool ascending = true) + { + return Connection.GetPageAsync( + predicate.ToPredicateGroup(), + new List { new Sort { Ascending = ascending, PropertyName = sortingProperty } }, + pageNumber, + itemsPerPage, + ActiveTransaction); + } + + public override Task CountAsync(Expression> predicate) + { + return Connection.CountAsync(predicate.ToPredicateGroup(), ActiveTransaction); } } } diff --git a/src/Stove.Dapper/Properties/AssemblyInfo.cs b/src/Stove.Dapper/Properties/AssemblyInfo.cs index 1e2e425..337bdcc 100644 --- a/src/Stove.Dapper/Properties/AssemblyInfo.cs +++ b/src/Stove.Dapper/Properties/AssemblyInfo.cs @@ -1,10 +1,10 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. + [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Stove.Dapper")] @@ -13,7 +13,9 @@ // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. + [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM + [assembly: Guid("5d02a99b-2e08-44c5-83fb-f607afa2b6ff")] diff --git a/src/Stove.Dapper/project.json b/src/Stove.Dapper/project.json index f7d1ab8..b6e372b 100644 --- a/src/Stove.Dapper/project.json +++ b/src/Stove.Dapper/project.json @@ -1,13 +1,13 @@ { - "version": "0.0.8-*", + "version": "0.0.9-*", "dependencies": { "Autofac": "4.3.0", "Dapper": "1.50.2", "DapperExtensions": "1.5.0", "EntityFramework": "6.1.3", - "Stove": "0.0.8", - "Stove.EntityFramework": "0.0.8-*" + "Stove": "0.0.9", + "Stove.EntityFramework": "0.0.9-*" }, "frameworks": { diff --git a/src/Stove.EntityFramework/EntityFramework/EfRepositoryRegistrar.cs b/src/Stove.EntityFramework/EntityFramework/EfRepositoryRegistrar.cs index db8176d..0b0acd8 100644 --- a/src/Stove.EntityFramework/EntityFramework/EfRepositoryRegistrar.cs +++ b/src/Stove.EntityFramework/EntityFramework/EfRepositoryRegistrar.cs @@ -28,7 +28,7 @@ public static void RegisterRepositories(Type dbContextType, IIocBuilder builder) ? autoRepositoryAttr.RepositoryImplementation.MakeGenericType(entityTypeInfo.EntityType) : autoRepositoryAttr.RepositoryImplementation.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType); - builder.RegisterServices(r => r.UseBuilder(cb => cb.RegisterType(implType).As(genericRepositoryType).AsImplementedInterfaces())); + builder.RegisterServices(r => r.UseBuilder(cb => cb.RegisterType(implType).As(genericRepositoryType).AsImplementedInterfaces().InjectPropertiesAsAutowired())); } else { @@ -38,7 +38,7 @@ public static void RegisterRepositories(Type dbContextType, IIocBuilder builder) ? autoRepositoryAttr.RepositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType) : autoRepositoryAttr.RepositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType, primaryKeyType); - builder.RegisterServices(r => r.UseBuilder(cb => cb.RegisterType(implType).As(genericRepositoryTypeWithPrimaryKey).AsImplementedInterfaces())); + builder.RegisterServices(r => r.UseBuilder(cb => cb.RegisterType(implType).As(genericRepositoryTypeWithPrimaryKey).AsImplementedInterfaces().InjectPropertiesAsAutowired())); } } } diff --git a/src/Stove.EntityFramework/EntityFramework/Extensions/QueryableExtensions.cs b/src/Stove.EntityFramework/EntityFramework/Extensions/QueryableExtensions.cs index 7d26680..3f1ea4d 100644 --- a/src/Stove.EntityFramework/EntityFramework/Extensions/QueryableExtensions.cs +++ b/src/Stove.EntityFramework/EntityFramework/Extensions/QueryableExtensions.cs @@ -3,18 +3,25 @@ using System.Linq; using System.Linq.Expressions; +using Autofac.Extras.IocManager; + +using Stove.Domain.Entities; +using Stove.Domain.Repositories; +using Stove.EntityFramework.EntityFramework.Interceptors; +using Stove.Extensions; + namespace Stove.EntityFramework.EntityFramework.Extensions { /// - /// Extension methods for and . + /// Extension methods for and . /// public static class QueryableExtensions { /// - /// Specifies the related objects to include in the query results. + /// Specifies the related objects to include in the query results. /// - /// The source on which to call Include. - /// A boolean value to determine to include or not. + /// The source on which to call Include. + /// A boolean value to determine to include or not. /// The dot-separated list of related objects to return in the query results. public static IQueryable IncludeIf(this IQueryable source, bool condition, string path) { @@ -24,10 +31,10 @@ public static IQueryable IncludeIf(this IQueryable source, bool condition, strin } /// - /// Specifies the related objects to include in the query results. + /// Specifies the related objects to include in the query results. /// - /// The source on which to call Include. - /// A boolean value to determine to include or not. + /// The source on which to call Include. + /// A boolean value to determine to include or not. /// The dot-separated list of related objects to return in the query results. public static IQueryable IncludeIf(this IQueryable source, bool condition, string path) { @@ -37,10 +44,10 @@ public static IQueryable IncludeIf(this IQueryable source, bool conditi } /// - /// Specifies the related objects to include in the query results. + /// Specifies the related objects to include in the query results. /// - /// The source on which to call Include. - /// A boolean value to determine to include or not. + /// The source on which to call Include. + /// A boolean value to determine to include or not. /// The type of navigation property being included. public static IQueryable IncludeIf(this IQueryable source, bool condition, Expression> path) { @@ -48,5 +55,54 @@ public static IQueryable IncludeIf(this IQueryable source, b ? source.Include(path) : source; } + + /// + /// Nolockings the specified queryable. + /// + /// + /// The type of the result. + /// The repository. + /// The queryable. + /// + public static TResult Nolocking(this IRepository repository, Func, TResult> queryable) where TEntity : class, IEntity + { + Check.NotNull(queryable, nameof(queryable)); + + TResult result; + using (IScopeResolver scopeResolver = repository.As().ScopeResolver.BeginScope()) + { + using (scopeResolver.Resolve().UseNolocking()) + { + result = queryable(repository.GetAll()); + } + } + + return result; + } + + /// + /// Nolockings the specified queryable. + /// + /// The type of the entity. + /// The type of the primary key. + /// The type of the result. + /// The repository. + /// The queryable. + /// + public static TResult Nolocking(this IRepository repository, Func, TResult> queryable) where TEntity : class, IEntity + { + Check.NotNull(queryable, nameof(queryable)); + + TResult result; + using (IScopeResolver scopeResolver = repository.As().ScopeResolver.BeginScope()) + { + using (scopeResolver.Resolve().UseNolocking()) + { + result = queryable(repository.GetAll()); + } + } + + return result; + } } -} \ No newline at end of file +} diff --git a/src/Stove.EntityFramework/EntityFramework/Interceptors/WithNolockInterceptor.cs b/src/Stove.EntityFramework/EntityFramework/Interceptors/WithNolockInterceptor.cs new file mode 100644 index 0000000..e2858d0 --- /dev/null +++ b/src/Stove.EntityFramework/EntityFramework/Interceptors/WithNolockInterceptor.cs @@ -0,0 +1,62 @@ +using System; +using System.Data.Common; +using System.Data.Entity.Infrastructure.Interception; +using System.Text.RegularExpressions; + +using Autofac.Extras.IocManager; + +using Stove.Runtime; + +namespace Stove.EntityFramework.EntityFramework.Interceptors +{ + public class WithNoLockInterceptor : DbCommandInterceptor, ITransientDependency + { + private const string InterceptionContextKey = "Stove.EntityFramework.Interceptors.WithNolockInterceptor"; + private static readonly Regex TableAliasRegex = new Regex(@"(?AS \[Extent\d+\](?! WITH \(NOLOCK\)))", RegexOptions.Multiline | RegexOptions.IgnoreCase); + + private readonly IAmbientScopeProvider _interceptionScopeProvider; + + public WithNoLockInterceptor(IAmbientScopeProvider interceptionScopeProvider) + { + _interceptionScopeProvider = interceptionScopeProvider; + } + + public InterceptionContext NolockingContext => _interceptionScopeProvider.GetValue(InterceptionContextKey); + + public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) + { + if (NolockingContext?.UseNolocking ?? false) + { + command.CommandText = TableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)"); + NolockingContext.CommandText = command.CommandText; + } + } + + public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) + { + if (NolockingContext?.UseNolocking ?? false) + { + command.CommandText = TableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)"); + NolockingContext.CommandText = command.CommandText; + } + } + + public IDisposable UseNolocking() + { + return _interceptionScopeProvider.BeginScope(InterceptionContextKey, new InterceptionContext(string.Empty, true)); + } + + public class InterceptionContext + { + public InterceptionContext(string commandText, bool useNolocking) + { + CommandText = commandText; + UseNolocking = useNolocking; + } + + public string CommandText { get; set; } + + public bool UseNolocking { get; set; } + } + } +} diff --git a/src/Stove.EntityFramework/project.json b/src/Stove.EntityFramework/project.json index 657e6cb..c775bdf 100644 --- a/src/Stove.EntityFramework/project.json +++ b/src/Stove.EntityFramework/project.json @@ -1,8 +1,8 @@ { - "version" : "0.0.8-*", + "version" : "0.0.9-*", "dependencies": { - "Stove": "0.0.8", + "Stove": "0.0.9", "EntityFramework": "6.1.3", "EntityFramework.DynamicFilters": "2.6.0", "FluentAssemblyScanner": "1.0.5", diff --git a/src/Stove.HangFire/project.json b/src/Stove.HangFire/project.json index 4567d64..a9bea48 100644 --- a/src/Stove.HangFire/project.json +++ b/src/Stove.HangFire/project.json @@ -1,8 +1,8 @@ { - "version": "0.0.8-*", + "version": "0.0.9-*", "dependencies": { - "Stove": "0.0.8", + "Stove": "0.0.9", "FluentAssemblyScanner": "1.0.5", "Hangfire": "1.6.8", "Hangfire.Core": "1.6.8", diff --git a/src/Stove.Mapster/project.json b/src/Stove.Mapster/project.json index a937c05..f55a679 100644 --- a/src/Stove.Mapster/project.json +++ b/src/Stove.Mapster/project.json @@ -1,9 +1,9 @@ { - "version" : "0.0.8-*", + "version" : "0.0.9-*", "dependencies" : { "Mapster" : "2.6.1", - "Stove" : "0.0.8" + "Stove" : "0.0.9" }, "frameworks" : { diff --git a/src/Stove.NLog/project.json b/src/Stove.NLog/project.json index 7a8a79e..8a29535 100644 --- a/src/Stove.NLog/project.json +++ b/src/Stove.NLog/project.json @@ -1,10 +1,10 @@ { - "version" : "0.0.8-*", + "version" : "0.0.9-*", "dependencies": { "Autofac": "4.3.0", - "NLog": "4.4.1", - "Stove": "0.0.8" + "NLog": "4.4.2", + "Stove": "0.0.9" }, "frameworks" : { diff --git a/src/Stove.RabbitMQ/project.json b/src/Stove.RabbitMQ/project.json index ef713b1..422fdef 100644 --- a/src/Stove.RabbitMQ/project.json +++ b/src/Stove.RabbitMQ/project.json @@ -1,11 +1,11 @@ -{ - "version": "0.0.8-*", +{ + "version": "0.0.9-*", "dependencies": { - "MassTransit": "3.5.4", - "MassTransit.Autofac": "3.5.4", - "MassTransit.RabbitMQ": "3.5.4", - "Stove": "0.0.8" + "MassTransit": "3.5.5", + "MassTransit.Autofac": "3.5.5", + "MassTransit.RabbitMQ": "3.5.5", + "Stove": "0.0.9" }, "frameworks": { diff --git a/src/Stove.Redis/project.json b/src/Stove.Redis/project.json index 878e5cd..2d3b5a1 100644 --- a/src/Stove.Redis/project.json +++ b/src/Stove.Redis/project.json @@ -1,10 +1,10 @@ { - "version" : "0.0.8-*", + "version" : "0.0.9-*", "dependencies": { "StackExchange.Redis": "1.2.0", "StackExchange.Redis.Extensions.Core": "2.1.0", - "Stove": "0.0.8" + "Stove": "0.0.9" }, "frameworks" : { diff --git a/src/Stove/Check.cs b/src/Stove/Check.cs index 53ad876..5665059 100644 --- a/src/Stove/Check.cs +++ b/src/Stove/Check.cs @@ -1,12 +1,16 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using JetBrains.Annotations; +using Stove.Collections.Extensions; +using Stove.Extensions; + namespace Stove { [DebuggerStepThrough] - internal static class Check + public static class Check { [ContractAnnotation("value:null => halt")] public static T NotNull(T value, [InvokerParameterName] [NotNull] string parameterName) @@ -18,5 +22,27 @@ public static T NotNull(T value, [InvokerParameterName] [NotNull] string para return value; } + + [ContractAnnotation("value:null => halt")] + public static string NotNullOrWhiteSpace(string value, [InvokerParameterName] [NotNull] string parameterName) + { + if (value.IsNullOrWhiteSpace()) + { + throw new ArgumentException($"{parameterName} can not be null, empty or white space!", parameterName); + } + + return value; + } + + [ContractAnnotation("value:null => halt")] + public static ICollection NotNullOrEmpty(ICollection value, [InvokerParameterName] [NotNull] string parameterName) + { + if (value.IsNullOrEmpty()) + { + throw new ArgumentException(parameterName + " can not be null or empty!", parameterName); + } + + return value; + } } } diff --git a/src/Stove/Domain/Repositories/IDapperRepositoryOfTEntityAndTPrimaryKey.cs b/src/Stove/Domain/Repositories/IDapperRepositoryOfTEntityAndTPrimaryKey.cs index beff25d..fcb7887 100644 --- a/src/Stove/Domain/Repositories/IDapperRepositoryOfTEntityAndTPrimaryKey.cs +++ b/src/Stove/Domain/Repositories/IDapperRepositoryOfTEntityAndTPrimaryKey.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; using System.Threading.Tasks; using Stove.Domain.Entities; @@ -46,6 +48,13 @@ public interface IDapperRepository : IRepository where TEn /// IEnumerable GetList(object predicate); + /// + /// Gets the list. + /// + /// The predicate. + /// + IEnumerable GetList(Expression> predicate); + /// /// Gets the list asynchronous. /// @@ -53,31 +62,56 @@ public interface IDapperRepository : IRepository where TEn /// Task> GetListAsync(object predicate); + /// + /// Gets the list asynchronous. + /// + /// The predicate. + /// + Task> GetListAsync(Expression> predicate); + /// /// Gets the list paged asynchronous. /// + /// The predicate. /// The page number. /// The items per page. - /// The conditions. - /// The order. + /// The sorting property. + /// if set to true [ascending]. + /// + Task> GetListPagedAsync(object predicate, int pageNumber, int itemsPerPage, string sortingProperty, bool ascending = true); + + /// + /// Gets the list paged asynchronous. + /// /// The predicate. + /// The page number. + /// The items per page. /// The sorting property. /// if set to true [ascending]. /// - Task> GetListPagedAsync(int pageNumber, int itemsPerPage, string conditions, string order, object predicate, string sortingProperty, bool ascending = true); + Task> GetListPagedAsync(Expression> predicate, int pageNumber, int itemsPerPage, string sortingProperty, bool ascending = true); /// /// Gets the list paged. /// + /// The predicate. /// The page number. /// The items per page. - /// The conditions. - /// The order. + /// The sorting property. + /// if set to true [ascending]. + /// + IEnumerable GetListPaged(object predicate, int pageNumber, int itemsPerPage, string sortingProperty, bool ascending = true); + + /// + /// Gets the list paged. + /// /// The predicate. + /// The page number. + /// The items per page. /// The sorting property. /// if set to true [ascending]. /// - IEnumerable GetListPaged(int pageNumber, int itemsPerPage, string conditions, string order, object predicate, string sortingProperty, bool ascending = true); + IEnumerable GetListPaged(Expression> predicate, int pageNumber, int itemsPerPage, string sortingProperty, bool ascending = true); /// /// Counts the specified predicate. @@ -86,6 +120,13 @@ public interface IDapperRepository : IRepository where TEn /// int Count(object predicate); + /// + /// Counts the specified predicate. + /// + /// The predicate. + /// + int Count(Expression> predicate); + /// /// Counts the asynchronous. /// @@ -93,6 +134,13 @@ public interface IDapperRepository : IRepository where TEn /// Task CountAsync(object predicate); + /// + /// Counts the asynchronous. + /// + /// The predicate. + /// + Task CountAsync(Expression> predicate); + /// /// Queries the specified query. /// @@ -118,7 +166,7 @@ public interface IDapperRepository : IRepository where TEn Task> QueryAsync(string query, object parameters) where TAny : class; /// - /// Queries the specified query. + /// Queries the specified query. /// /// The type of any. /// The query. @@ -126,7 +174,7 @@ public interface IDapperRepository : IRepository where TEn IEnumerable Query(string query) where TAny : class; /// - /// Queries the specified query. + /// Queries the specified query. /// /// The type of any. /// The query. @@ -152,6 +200,17 @@ public interface IDapperRepository : IRepository where TEn /// IEnumerable GetSet(object predicate, int firstResult, int maxResults, string sortingProperty, bool ascending = true); + /// + /// Gets the set. + /// + /// The predicate. + /// The first result. + /// The maximum results. + /// The sorting property. + /// if set to true [ascending]. + /// + IEnumerable GetSet(Expression> predicate, int firstResult, int maxResults, string sortingProperty, bool ascending = true); + /// /// Gets the set asynchronous. /// @@ -162,5 +221,16 @@ public interface IDapperRepository : IRepository where TEn /// if set to true [ascending]. /// Task> GetSetAsync(object predicate, int firstResult, int maxResults, string sortingProperty, bool ascending = true); + + /// + /// Gets the set asynchronous. + /// + /// The predicate. + /// The first result. + /// The maximum results. + /// The sorting property. + /// if set to true [ascending]. + /// + Task> GetSetAsync(Expression> predicate, int firstResult, int maxResults, string sortingProperty, bool ascending = true); } } diff --git a/src/Stove/Domain/Repositories/IStoveRepositoryBaseWithResolver.cs b/src/Stove/Domain/Repositories/IStoveRepositoryBaseWithResolver.cs new file mode 100644 index 0000000..253dc3b --- /dev/null +++ b/src/Stove/Domain/Repositories/IStoveRepositoryBaseWithResolver.cs @@ -0,0 +1,9 @@ +using Autofac.Extras.IocManager; + +namespace Stove.Domain.Repositories +{ + public interface IStoveRepositoryBaseWithResolver + { + IScopeResolver ScopeResolver { get; set; } + } +} diff --git a/src/Stove/Domain/Repositories/StoveDapperRepositoryBase.cs b/src/Stove/Domain/Repositories/StoveDapperRepositoryBase.cs index 48a42c4..75e6377 100644 --- a/src/Stove/Domain/Repositories/StoveDapperRepositoryBase.cs +++ b/src/Stove/Domain/Repositories/StoveDapperRepositoryBase.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; using System.Threading.Tasks; using Stove.Domain.Entities; @@ -63,11 +65,39 @@ public virtual Task> GetSetAsync(object predicate, int firs return Task.FromResult(GetSet(predicate, firstResult, maxResults, sortingProperty, ascending)); } - public Task> GetListPagedAsync(int pageNumber, int itemsPerPage, string conditions, string order, object predicate, string sortingProperty, bool ascending = true) + public virtual Task> GetListPagedAsync(object predicate, int pageNumber, int itemsPerPage, string sortingProperty, bool ascending = true) { - return Task.FromResult(GetListPaged(pageNumber, itemsPerPage, conditions, order, predicate, sortingProperty, ascending)); + return Task.FromResult(GetListPaged(predicate, pageNumber, itemsPerPage, sortingProperty, ascending)); } - public abstract IEnumerable GetListPaged(int pageNumber, int itemsPerPage, string conditions, string order, object predicate, string sortingProperty, bool ascending = true); + public abstract IEnumerable GetListPaged(object predicate, int pageNumber, int itemsPerPage, string sortingProperty, bool ascending = true); + + public abstract IEnumerable GetList(Expression> predicate); + + public virtual Task> GetListAsync(Expression> predicate) + { + return Task.FromResult(GetList(predicate)); + } + + public virtual Task> GetListPagedAsync(Expression> predicate, int pageNumber, int itemsPerPage, string sortingProperty, bool ascending = true) + { + return Task.FromResult(GetListPaged(predicate, pageNumber, itemsPerPage, sortingProperty, ascending)); + } + + public abstract IEnumerable GetListPaged(Expression> predicate, int pageNumber, int itemsPerPage, string sortingProperty, bool ascending = true); + + public abstract int Count(Expression> predicate); + + public virtual Task CountAsync(Expression> predicate) + { + return Task.FromResult(Count(predicate)); + } + + public abstract IEnumerable GetSet(Expression> predicate, int firstResult, int maxResults, string sortingProperty, bool ascending = true); + + public virtual Task> GetSetAsync(Expression> predicate, int firstResult, int maxResults, string sortingProperty, bool ascending = true) + { + return Task.FromResult(GetSet(predicate, firstResult, maxResults, sortingProperty, ascending)); + } } } diff --git a/src/Stove/Domain/Repositories/StoveRepositoryBase.cs b/src/Stove/Domain/Repositories/StoveRepositoryBase.cs index e387669..25a80c2 100644 --- a/src/Stove/Domain/Repositories/StoveRepositoryBase.cs +++ b/src/Stove/Domain/Repositories/StoveRepositoryBase.cs @@ -4,6 +4,8 @@ using System.Linq.Expressions; using System.Threading.Tasks; +using Autofac.Extras.IocManager; + using Stove.Domain.Entities; namespace Stove.Domain.Repositories @@ -14,9 +16,11 @@ namespace Stove.Domain.Repositories /// /// Type of the Entity for this repository /// Primary key of the entity - public abstract class StoveRepositoryBase : IRepository + public abstract class StoveRepositoryBase : IRepository, IStoveRepositoryBaseWithResolver where TEntity : class, IEntity { + public IScopeResolver ScopeResolver { get; set; } + public abstract IQueryable GetAll(); public virtual IQueryable GetAllIncluding(params Expression>[] propertySelectors) diff --git a/src/Stove/Domain/Services/DomainService.cs b/src/Stove/Domain/Services/DomainService.cs new file mode 100644 index 0000000..95c4a23 --- /dev/null +++ b/src/Stove/Domain/Services/DomainService.cs @@ -0,0 +1,11 @@ +namespace Stove.Domain.Services +{ + /// + /// This class can be used as a base class for domain services. + /// + /// + /// + public abstract class DomainService : StoveServiceBase, IDomainService + { + } +} diff --git a/src/Stove/Domain/Services/IDomainService.cs b/src/Stove/Domain/Services/IDomainService.cs new file mode 100644 index 0000000..8fc4658 --- /dev/null +++ b/src/Stove/Domain/Services/IDomainService.cs @@ -0,0 +1,12 @@ +using Autofac.Extras.IocManager; + +namespace Stove.Domain.Services +{ + /// + /// This interface must be implemented by all domain services to identify them by convention. + /// + /// + public interface IDomainService : ITransientDependency + { + } +} diff --git a/src/Stove/JetBrains/Annotations/CodeAnnotations.cs b/src/Stove/JetBrains/Annotations/CodeAnnotations.cs deleted file mode 100644 index 69c9f8a..0000000 --- a/src/Stove/JetBrains/Annotations/CodeAnnotations.cs +++ /dev/null @@ -1,1251 +0,0 @@ -using System; - -namespace JetBrains.Annotations -{ - /* MIT License - - Copyright (c) 2016 JetBrains http://www.jetbrains.com - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. */ -#pragma warning disable 1591 - - // ReSharper disable UnusedMember.Global - // ReSharper disable MemberCanBePrivate.Global - // ReSharper disable UnusedAutoPropertyAccessor.Global - // ReSharper disable IntroduceOptionalParameters.Global - // ReSharper disable MemberCanBeProtected.Global - // ReSharper disable InconsistentNaming - - /// - /// Indicates that the value of the marked element could be null sometimes, - /// so the check for null is necessary before its usage. - /// - /// - /// - /// [CanBeNull] object Test() => null; - /// - /// void UseTest() { - /// var p = Test(); - /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' - /// } - /// - /// - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] - internal sealed class CanBeNullAttribute : Attribute - { - } - - /// - /// Indicates that the value of the marked element could never be null. - /// - /// - /// - /// [NotNull] object Foo() { - /// return null; // Warning: Possible 'null' assignment - /// } - /// - /// - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] - internal sealed class NotNullAttribute : Attribute - { - } - - /// - /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task - /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property - /// or of the Lazy.Value property can never be null. - /// - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field)] - internal sealed class ItemNotNullAttribute : Attribute - { - } - - /// - /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task - /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property - /// or of the Lazy.Value property can be null. - /// - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | - AttributeTargets.Delegate | AttributeTargets.Field)] - internal sealed class ItemCanBeNullAttribute : Attribute - { - } - - /// - /// Indicates that the marked method builds string by format pattern and (optional) arguments. - /// Parameter, which contains format string, should be given in constructor. The format string - /// should be in -like form. - /// - /// - /// - /// [StringFormatMethod("message")] - /// void ShowError(string message, params object[] args) { /* do something */ } - /// - /// void Foo() { - /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string - /// } - /// - /// - [AttributeUsage( - AttributeTargets.Constructor | AttributeTargets.Method | - AttributeTargets.Property | AttributeTargets.Delegate)] - internal sealed class StringFormatMethodAttribute : Attribute - { - /// - /// Specifies which parameter of an annotated method should be treated as format-string - /// - public StringFormatMethodAttribute([NotNull] string formatParameterName) - { - FormatParameterName = formatParameterName; - } - - [NotNull] - public string FormatParameterName { get; private set; } - } - - /// - /// For a parameter that is expected to be one of the limited set of values. - /// Specify fields of which type should be used as values for this parameter. - /// - [AttributeUsage( - AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, - AllowMultiple = true)] - internal sealed class ValueProviderAttribute : Attribute - { - public ValueProviderAttribute([NotNull] string name) - { - Name = name; - } - - [NotNull] - public string Name { get; private set; } - } - - /// - /// Indicates that the function argument should be string literal and match one - /// of the parameters of the caller function. For example, ReSharper annotates - /// the parameter of . - /// - /// - /// - /// void Foo(string param) { - /// if (param == null) - /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol - /// } - /// - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class InvokerParameterNameAttribute : Attribute - { - } - - /// - /// Indicates that the method is contained in a type that implements - /// System.ComponentModel.INotifyPropertyChanged interface and this method - /// is used to notify that some property value changed. - /// - /// - /// The method should be non-static and conform to one of the supported signatures: - /// - /// - /// NotifyChanged(string) - /// - /// - /// NotifyChanged(params string[]) - /// - /// - /// NotifyChanged{T}(Expression{Func{T}}) - /// - /// - /// NotifyChanged{T,U}(Expression{Func{T,U}}) - /// - /// - /// SetProperty{T}(ref T, T, string) - /// - /// - /// - /// - /// - /// public class Foo : INotifyPropertyChanged { - /// public event PropertyChangedEventHandler PropertyChanged; - /// - /// [NotifyPropertyChangedInvocator] - /// protected virtual void NotifyChanged(string propertyName) { ... } - /// - /// string _name; - /// - /// public string Name { - /// get { return _name; } - /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } - /// } - /// } - /// - /// Examples of generated notifications: - /// - /// - /// NotifyChanged("Property") - /// - /// - /// NotifyChanged(() => Property) - /// - /// - /// NotifyChanged((VM x) => x.Property) - /// - /// - /// SetProperty(ref myField, value, "Property") - /// - /// - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class NotifyPropertyChangedInvocatorAttribute : Attribute - { - public NotifyPropertyChangedInvocatorAttribute() - { - } - - public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) - { - ParameterName = parameterName; - } - - [CanBeNull] - public string ParameterName { get; private set; } - } - - /// - /// Describes dependency between method input and output. - /// - /// - ///

Function Definition Table syntax:

- /// - /// FDT ::= FDTRow [;FDTRow]* - /// FDTRow ::= Input => Output | Output <= Input - /// Input ::= ParameterName: Value [, Input]* - /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} - /// Value ::= true | false | null | notnull | canbenull - /// - /// If method has single input parameter, it's name could be omitted.
- /// Using halt (or void/nothing, which is the same) for method output - /// means that the methos doesn't return normally (throws or terminates the process).
- /// Value canbenull is only applicable for output parameters.
- /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute - /// with rows separated by semicolon. There is no notion of order rows, all rows are checked - /// for applicability and applied per each program state tracked by R# analysis.
- ///
- /// - /// - /// - /// - /// [ContractAnnotation("=> halt")] - /// public void TerminationMethod() - /// - /// - /// - /// - /// [ContractAnnotation("halt <= condition: false")] - /// public void Assert(bool condition, string text) // regular assertion method - /// - /// - /// - /// - /// [ContractAnnotation("s:null => true")] - /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() - /// - /// - /// - /// - /// // A method that returns null if the parameter is null, - /// // and not null if the parameter is not null - /// [ContractAnnotation("null => null; notnull => notnull")] - /// public object Transform(object data) - /// - /// - /// - /// - /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] - /// public bool TryParse(string s, out Person result) - /// - /// - /// - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - internal sealed class ContractAnnotationAttribute : Attribute - { - public ContractAnnotationAttribute([NotNull] string contract) - : this(contract, false) - { - } - - public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) - { - Contract = contract; - ForceFullStates = forceFullStates; - } - - [NotNull] - public string Contract { get; private set; } - - public bool ForceFullStates { get; private set; } - } - - /// - /// Indicates that marked element should be localized or not. - /// - /// - /// - /// [LocalizationRequiredAttribute(true)] - /// class Foo { - /// string str = "my string"; // Warning: Localizable string - /// } - /// - /// - [AttributeUsage(AttributeTargets.All)] - internal sealed class LocalizationRequiredAttribute : Attribute - { - public LocalizationRequiredAttribute() : this(true) - { - } - - public LocalizationRequiredAttribute(bool required) - { - Required = required; - } - - public bool Required { get; private set; } - } - - /// - /// Indicates that the value of the marked type (or its derivatives) - /// cannot be compared using '==' or '!=' operators and Equals() - /// should be used instead. However, using '==' or '!=' for comparison - /// with null is always permitted. - /// - /// - /// - /// [CannotApplyEqualityOperator] - /// class NoEquality { } - /// - /// class UsesNoEquality { - /// void Test() { - /// var ca1 = new NoEquality(); - /// var ca2 = new NoEquality(); - /// if (ca1 != null) { // OK - /// bool condition = ca1 == ca2; // Warning - /// } - /// } - /// } - /// - /// - [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] - internal sealed class CannotApplyEqualityOperatorAttribute : Attribute - { - } - - /// - /// When applied to a target attribute, specifies a requirement for any type marked - /// with the target attribute to implement or inherit specific type or types. - /// - /// - /// - /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement - /// class ComponentAttribute : Attribute { } - /// - /// [Component] // ComponentAttribute requires implementing IComponent interface - /// class MyComponent : IComponent { } - /// - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - [BaseTypeRequired(typeof(Attribute))] - internal sealed class BaseTypeRequiredAttribute : Attribute - { - public BaseTypeRequiredAttribute([NotNull] Type baseType) - { - BaseType = baseType; - } - - [NotNull] - public Type BaseType { get; private set; } - } - - /// - /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), - /// so this symbol will not be marked as unused (as well as by other usage inspections). - /// - [AttributeUsage(AttributeTargets.All)] - internal sealed class UsedImplicitlyAttribute : Attribute - { - public UsedImplicitlyAttribute() - : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) - { - } - - public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) - : this(useKindFlags, ImplicitUseTargetFlags.Default) - { - } - - public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) - : this(ImplicitUseKindFlags.Default, targetFlags) - { - } - - public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) - { - UseKindFlags = useKindFlags; - TargetFlags = targetFlags; - } - - public ImplicitUseKindFlags UseKindFlags { get; private set; } - - public ImplicitUseTargetFlags TargetFlags { get; private set; } - } - - /// - /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes - /// as unused (as well as by other usage inspections) - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] - internal sealed class MeansImplicitUseAttribute : Attribute - { - public MeansImplicitUseAttribute() - : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) - { - } - - public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) - : this(useKindFlags, ImplicitUseTargetFlags.Default) - { - } - - public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) - : this(ImplicitUseKindFlags.Default, targetFlags) - { - } - - public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) - { - UseKindFlags = useKindFlags; - TargetFlags = targetFlags; - } - - [UsedImplicitly] - public ImplicitUseKindFlags UseKindFlags { get; private set; } - - [UsedImplicitly] - public ImplicitUseTargetFlags TargetFlags { get; private set; } - } - - [Flags] - internal enum ImplicitUseKindFlags - { - Default = Access | Assign | InstantiatedWithFixedConstructorSignature, - - /// Only entity marked with attribute considered used. - Access = 1, - - /// Indicates implicit assignment to a member. - Assign = 2, - - /// - /// Indicates implicit instantiation of a type with fixed constructor signature. - /// That means any unused constructor parameters won't be reported as such. - /// - InstantiatedWithFixedConstructorSignature = 4, - - /// Indicates implicit instantiation of a type. - InstantiatedNoFixedConstructorSignature = 8 - } - - /// - /// Specify what is considered used implicitly when marked - /// with or . - /// - [Flags] - internal enum ImplicitUseTargetFlags - { - Default = Itself, - Itself = 1, - - /// Members of entity marked with attribute are considered used. - Members = 2, - - /// Entity marked with attribute and all its members considered used. - WithMembers = Itself | Members - } - - /// - /// This attribute is intended to mark publicly available API - /// which should not be removed and so is treated as used. - /// - [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] - internal sealed class PublicAPIAttribute : Attribute - { - public PublicAPIAttribute() - { - } - - public PublicAPIAttribute([NotNull] string comment) - { - Comment = comment; - } - - [CanBeNull] - public string Comment { get; private set; } - } - - /// - /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. - /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. - /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class InstantHandleAttribute : Attribute - { - } - - /// - /// Indicates that a method does not make any observable state changes. - /// The same as System.Diagnostics.Contracts.PureAttribute. - /// - /// - /// - /// [Pure] int Multiply(int x, int y) => x * y; - /// - /// void M() { - /// Multiply(123, 42); // Waring: Return value of pure method is not used - /// } - /// - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class PureAttribute : Attribute - { - } - - /// - /// Indicates that the return value of method invocation must be used. - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class MustUseReturnValueAttribute : Attribute - { - public MustUseReturnValueAttribute() - { - } - - public MustUseReturnValueAttribute([NotNull] string justification) - { - Justification = justification; - } - - [CanBeNull] - public string Justification { get; private set; } - } - - /// - /// Indicates the type member or parameter of some type, that should be used instead of all other ways - /// to get the value that type. This annotation is useful when you have some "context" value evaluated - /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. - /// - /// - /// - /// class Foo { - /// [ProvidesContext] IBarService _barService = ...; - /// - /// void ProcessNode(INode node) { - /// DoSomething(node, node.GetGlobalServices().Bar); - /// // ^ Warning: use value of '_barService' field - /// } - /// } - /// - /// - [AttributeUsage( - AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] - internal sealed class ProvidesContextAttribute : Attribute - { - } - - /// - /// Indicates that a parameter is a path to a file or a folder within a web project. - /// Path can be relative or absolute, starting from web root (~). - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class PathReferenceAttribute : Attribute - { - public PathReferenceAttribute() - { - } - - public PathReferenceAttribute([NotNull] [PathReference] string basePath) - { - BasePath = basePath; - } - - [CanBeNull] - public string BasePath { get; private set; } - } - - /// - /// An extension method marked with this attribute is processed by ReSharper code completion - /// as a 'Source Template'. When extension method is completed over some expression, it's source code - /// is automatically expanded like a template at call site. - /// - /// - /// Template method body can contain valid source code and/or special comments starting with '$'. - /// Text inside these comments is added as source code when the template is applied. Template parameters - /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs. - /// Use the attribute to specify macros for parameters. - /// - /// - /// In this example, the 'forEach' method is a source template available over all values - /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block: - /// - /// [SourceTemplate] - /// public static void forEach<T>(this IEnumerable<T> xs) { - /// foreach (var x in xs) { - /// //$ $END$ - /// } - /// } - /// - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class SourceTemplateAttribute : Attribute - { - } - - /// - /// Allows specifying a macro for a parameter of a source template. - /// - /// - /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression - /// is defined in the property. When applied on a method, the target - /// template parameter is defined in the property. To apply the macro silently - /// for the parameter, set the property value = -1. - /// - /// - /// Applying the attribute on a source template method: - /// - /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")] - /// public static void forEach<T>(this IEnumerable<T> collection) { - /// foreach (var item in collection) { - /// //$ $END$ - /// } - /// } - /// - /// Applying the attribute on a template method parameter: - /// - /// [SourceTemplate] - /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) { - /// /*$ var $x$Id = "$newguid$" + x.ToString(); - /// x.DoSomething($x$Id); */ - /// } - /// - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)] - internal sealed class MacroAttribute : Attribute - { - /// - /// Allows specifying a macro that will be executed for a source template - /// parameter when the template is expanded. - /// - [CanBeNull] - public string Expression { get; set; } - - /// - /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. - /// - /// - /// If the target parameter is used several times in the template, only one occurrence becomes editable; - /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, - /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. - /// - /// > - public int Editable { get; set; } - - /// - /// Identifies the target parameter of a source template if the - /// is applied on a template method. - /// - [CanBeNull] - public string Target { get; set; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - internal sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute - { - public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; private set; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - internal sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute - { - public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; private set; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - internal sealed class AspMvcAreaViewLocationFormatAttribute : Attribute - { - public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; private set; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - internal sealed class AspMvcMasterLocationFormatAttribute : Attribute - { - public AspMvcMasterLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; private set; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - internal sealed class AspMvcPartialViewLocationFormatAttribute : Attribute - { - public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; private set; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - internal sealed class AspMvcViewLocationFormatAttribute : Attribute - { - public AspMvcViewLocationFormatAttribute([NotNull] string format) - { - Format = format; - } - - [NotNull] - public string Format { get; private set; } - } - - /// - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC action. If applied to a method, the MVC action name is calculated - /// implicitly from the context. Use this attribute for custom wrappers similar to - /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - internal sealed class AspMvcActionAttribute : Attribute - { - public AspMvcActionAttribute() - { - } - - public AspMvcActionAttribute([NotNull] string anonymousProperty) - { - AnonymousProperty = anonymousProperty; - } - - [CanBeNull] - public string AnonymousProperty { get; private set; } - } - - /// - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. - /// Use this attribute for custom wrappers similar to - /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class AspMvcAreaAttribute : Attribute - { - public AspMvcAreaAttribute() - { - } - - public AspMvcAreaAttribute([NotNull] string anonymousProperty) - { - AnonymousProperty = anonymousProperty; - } - - [CanBeNull] - public string AnonymousProperty { get; private set; } - } - - /// - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is - /// an MVC controller. If applied to a method, the MVC controller name is calculated - /// implicitly from the context. Use this attribute for custom wrappers similar to - /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - internal sealed class AspMvcControllerAttribute : Attribute - { - public AspMvcControllerAttribute() - { - } - - public AspMvcControllerAttribute([NotNull] string anonymousProperty) - { - AnonymousProperty = anonymousProperty; - } - - [CanBeNull] - public string AnonymousProperty { get; private set; } - } - - /// - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute - /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class AspMvcMasterAttribute : Attribute - { - } - - /// - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute - /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class AspMvcModelTypeAttribute : Attribute - { - } - - /// - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC - /// partial view. If applied to a method, the MVC partial view name is calculated implicitly - /// from the context. Use this attribute for custom wrappers similar to - /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - internal sealed class AspMvcPartialViewAttribute : Attribute - { - } - - /// - /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - internal sealed class AspMvcSuppressViewErrorAttribute : Attribute - { - } - - /// - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. - /// Use this attribute for custom wrappers similar to - /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class AspMvcDisplayTemplateAttribute : Attribute - { - } - - /// - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. - /// Use this attribute for custom wrappers similar to - /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class AspMvcEditorTemplateAttribute : Attribute - { - } - - /// - /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. - /// Use this attribute for custom wrappers similar to - /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class AspMvcTemplateAttribute : Attribute - { - } - - /// - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly - /// from the context. Use this attribute for custom wrappers similar to - /// System.Web.Mvc.Controller.View(Object). - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - internal sealed class AspMvcViewAttribute : Attribute - { - } - - /// - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC view component name. - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class AspMvcViewComponentAttribute : Attribute - { - } - - /// - /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter - /// is an MVC view component view. If applied to a method, the MVC view component view name is default. - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - internal sealed class AspMvcViewComponentViewAttribute : Attribute - { - } - - /// - /// ASP.NET MVC attribute. When applied to a parameter of an attribute, - /// indicates that this parameter is an MVC action name. - /// - /// - /// - /// [ActionName("Foo")] - /// public ActionResult Login(string returnUrl) { - /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK - /// return RedirectToAction("Bar"); // Error: Cannot resolve action - /// } - /// - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] - internal sealed class AspMvcActionSelectorAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] - internal sealed class HtmlElementAttributesAttribute : Attribute - { - public HtmlElementAttributesAttribute() - { - } - - public HtmlElementAttributesAttribute([NotNull] string name) - { - Name = name; - } - - [CanBeNull] - public string Name { get; private set; } - } - - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] - internal sealed class HtmlAttributeValueAttribute : Attribute - { - public HtmlAttributeValueAttribute([NotNull] string name) - { - Name = name; - } - - [NotNull] - public string Name { get; private set; } - } - - /// - /// Razor attribute. Indicates that a parameter or a method is a Razor section. - /// Use this attribute for custom wrappers similar to - /// System.Web.WebPages.WebPageBase.RenderSection(String). - /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] - internal sealed class RazorSectionAttribute : Attribute - { - } - - /// - /// Indicates how method, constructor invocation or property access - /// over collection type affects content of the collection. - /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] - internal sealed class CollectionAccessAttribute : Attribute - { - public CollectionAccessAttribute(CollectionAccessType collectionAccessType) - { - CollectionAccessType = collectionAccessType; - } - - public CollectionAccessType CollectionAccessType { get; private set; } - } - - [Flags] - internal enum CollectionAccessType - { - /// Method does not use or modify content of the collection. - None = 0, - - /// Method only reads content of the collection but does not modify it. - Read = 1, - - /// Method can change content of the collection but does not add new elements. - ModifyExistingContent = 2, - - /// Method can add new elements to the collection. - UpdatedContent = ModifyExistingContent | 4 - } - - /// - /// Indicates that the marked method is assertion method, i.e. it halts control flow if - /// one of the conditions is satisfied. To set the condition, mark one of the parameters with - /// attribute. - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class AssertionMethodAttribute : Attribute - { - } - - /// - /// Indicates the condition parameter of the assertion method. The method itself should be - /// marked by attribute. The mandatory argument of - /// the attribute is the assertion type. - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class AssertionConditionAttribute : Attribute - { - public AssertionConditionAttribute(AssertionConditionType conditionType) - { - ConditionType = conditionType; - } - - public AssertionConditionType ConditionType { get; private set; } - } - - /// - /// Specifies assertion type. If the assertion method argument satisfies the condition, - /// then the execution continues. Otherwise, execution is assumed to be halted. - /// - internal enum AssertionConditionType - { - /// Marked parameter should be evaluated to true. - IS_TRUE = 0, - - /// Marked parameter should be evaluated to false. - IS_FALSE = 1, - - /// Marked parameter should be evaluated to null value. - IS_NULL = 2, - - /// Marked parameter should be evaluated to not null value. - IS_NOT_NULL = 3 - } - - /// - /// Indicates that the marked method unconditionally terminates control flow execution. - /// For example, it could unconditionally throw exception. - /// - [Obsolete("Use [ContractAnnotation('=> halt')] instead")] - [AttributeUsage(AttributeTargets.Method)] - internal sealed class TerminatesProgramAttribute : Attribute - { - } - - /// - /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, - /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters - /// of delegate type by analyzing LINQ method chains. - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class LinqTunnelAttribute : Attribute - { - } - - /// - /// Indicates that IEnumerable, passed as parameter, is not enumerated. - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class NoEnumerationAttribute : Attribute - { - } - - /// - /// Indicates that parameter is regular expression pattern. - /// - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class RegexPatternAttribute : Attribute - { - } - - /// - /// Prevents the Member Reordering feature from tossing members of the marked class. - /// - /// - /// The attribute must be mentioned in your member reordering patterns - /// - [AttributeUsage( - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] - internal sealed class NoReorderAttribute : Attribute - { - } - - /// - /// XAML attribute. Indicates the type that has ItemsSource property and should be treated - /// as ItemsControl-derived type, to enable inner items DataContext type resolve. - /// - [AttributeUsage(AttributeTargets.Class)] - internal sealed class XamlItemsControlAttribute : Attribute - { - } - - /// - /// XAML attribute. Indicates the property of some BindingBase-derived type, that - /// is used to bind some item of ItemsControl-derived type. This annotation will - /// enable the DataContext type resolve for XAML bindings for such properties. - /// - /// - /// Property should have the tree ancestor of the ItemsControl type or - /// marked with the attribute. - /// - [AttributeUsage(AttributeTargets.Property)] - internal sealed class XamlItemBindingOfItemsControlAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - internal sealed class AspChildControlTypeAttribute : Attribute - { - public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) - { - TagName = tagName; - ControlType = controlType; - } - - [NotNull] - public string TagName { get; private set; } - - [NotNull] - public Type ControlType { get; private set; } - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] - internal sealed class AspDataFieldAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] - internal sealed class AspDataFieldsAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - internal sealed class AspMethodPropertyAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - internal sealed class AspRequiredAttributeAttribute : Attribute - { - public AspRequiredAttributeAttribute([NotNull] string attribute) - { - Attribute = attribute; - } - - [NotNull] - public string Attribute { get; private set; } - } - - [AttributeUsage(AttributeTargets.Property)] - internal sealed class AspTypePropertyAttribute : Attribute - { - public AspTypePropertyAttribute(bool createConstructorReferences) - { - CreateConstructorReferences = createConstructorReferences; - } - - public bool CreateConstructorReferences { get; private set; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - internal sealed class RazorImportNamespaceAttribute : Attribute - { - public RazorImportNamespaceAttribute([NotNull] string name) - { - Name = name; - } - - [NotNull] - public string Name { get; private set; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - internal sealed class RazorInjectionAttribute : Attribute - { - public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) - { - Type = type; - FieldName = fieldName; - } - - [NotNull] - public string Type { get; private set; } - - [NotNull] - public string FieldName { get; private set; } - } - - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - internal sealed class RazorDirectiveAttribute : Attribute - { - public RazorDirectiveAttribute([NotNull] string directive) - { - Directive = directive; - } - - [NotNull] - public string Directive { get; private set; } - } - - [AttributeUsage(AttributeTargets.Method)] - internal sealed class RazorHelperCommonAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - internal sealed class RazorLayoutAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Method)] - internal sealed class RazorWriteLiteralMethodAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Method)] - internal sealed class RazorWriteMethodAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class RazorWriteMethodParameterAttribute : Attribute - { - } -} diff --git a/src/Stove/Runtime/IAmbientDataContext.cs b/src/Stove/Runtime/IAmbientDataContext.cs new file mode 100644 index 0000000..bbbf2fa --- /dev/null +++ b/src/Stove/Runtime/IAmbientDataContext.cs @@ -0,0 +1,9 @@ +namespace Stove.Runtime +{ + public interface IAmbientDataContext + { + void SetData(string key, object value); + + object GetData(string key); + } +} diff --git a/src/Stove/Runtime/IAmbientScopeProvider.cs b/src/Stove/Runtime/IAmbientScopeProvider.cs new file mode 100644 index 0000000..b70cb8f --- /dev/null +++ b/src/Stove/Runtime/IAmbientScopeProvider.cs @@ -0,0 +1,12 @@ +using System; + +namespace Stove.Runtime +{ + public interface IAmbientScopeProvider + where T : class + { + T GetValue(string contextKey); + + IDisposable BeginScope(string contextKey, T value); + } +} diff --git a/src/Stove/Runtime/Remoting/CallContextAmbientDataContext.cs b/src/Stove/Runtime/Remoting/CallContextAmbientDataContext.cs new file mode 100644 index 0000000..ec5ffc9 --- /dev/null +++ b/src/Stove/Runtime/Remoting/CallContextAmbientDataContext.cs @@ -0,0 +1,26 @@ +using System.Runtime.Remoting.Messaging; + +using Autofac.Extras.IocManager; + +namespace Stove.Runtime.Remoting +{ + //TODO: We can switch to AsyncLocal once we support .Net 4.6.1. + public class CallContextAmbientDataContext : IAmbientDataContext, ISingletonDependency + { + public void SetData(string key, object value) + { + if (value == null) + { + CallContext.FreeNamedDataSlot(key); + return; + } + + CallContext.LogicalSetData(key, value); + } + + public object GetData(string key) + { + return CallContext.LogicalGetData(key); + } + } +} diff --git a/src/Stove/Runtime/Remoting/DataContextAmbientScopeProvider.cs b/src/Stove/Runtime/Remoting/DataContextAmbientScopeProvider.cs new file mode 100644 index 0000000..0de9e15 --- /dev/null +++ b/src/Stove/Runtime/Remoting/DataContextAmbientScopeProvider.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Concurrent; + +using JetBrains.Annotations; + +using Stove.Collections.Extensions; +using Stove.Domain.Uow; +using Stove.Log; + +namespace Stove.Runtime.Remoting +{ + /// + /// CallContext implementation of . + /// This is the default implementation. + /// + public class DataContextAmbientScopeProvider : IAmbientScopeProvider + where T : class + { + private static readonly ConcurrentDictionary ScopeDictionary = new ConcurrentDictionary(); + + private readonly IAmbientDataContext _dataContext; + + public DataContextAmbientScopeProvider([NotNull] IAmbientDataContext dataContext) + { + Check.NotNull(dataContext, nameof(dataContext)); + + _dataContext = dataContext; + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public T GetValue(string contextKey) + { + return GetCurrentItem(contextKey)?.Value; + } + + public IDisposable BeginScope(string contextKey, T value) + { + var item = new ScopeItem(value, GetCurrentItem(contextKey)); + + if (!ScopeDictionary.TryAdd(item.Id, item)) + { + throw new StoveException("Can not set unit of work! ScopeDictionary.TryAdd returns false!"); + } + + _dataContext.SetData(contextKey, item.Id); + + return new DisposeAction(() => + { + ScopeDictionary.TryRemove(item.Id, out item); + + if (item.Outer == null) + { + _dataContext.SetData(contextKey, null); + return; + } + + _dataContext.SetData(contextKey, item.Outer.Id); + }); + } + + private ScopeItem GetCurrentItem(string contextKey) + { + var objKey = _dataContext.GetData(contextKey) as string; + return objKey != null ? ScopeDictionary.GetOrDefault(objKey) : null; + } + + private class ScopeItem + { + public ScopeItem(T value, ScopeItem outer = null) + { + Id = Guid.NewGuid().ToString(); + + Value = value; + Outer = outer; + } + + public string Id { get; } + + public ScopeItem Outer { get; } + + public T Value { get; } + } + } +} diff --git a/src/Stove/Runtime/Session/ClaimsStoveSession.cs b/src/Stove/Runtime/Session/ClaimsStoveSession.cs index 6b9a023..7dcb1f4 100644 --- a/src/Stove/Runtime/Session/ClaimsStoveSession.cs +++ b/src/Stove/Runtime/Session/ClaimsStoveSession.cs @@ -13,19 +13,27 @@ namespace Stove.Runtime.Session /// Implements to get session properties from claims of /// . /// - public class ClaimsStoveSession : IStoveSession, ISingletonDependency + public class ClaimsStoveSession : StoveSessionBase, ISingletonDependency { - public ClaimsStoveSession() + public ClaimsStoveSession( + IAmbientScopeProvider sessionOverrideScopeProvider, + IPrincipalAccessor principalAccessor) + : base(sessionOverrideScopeProvider) { - PrincipalAccessor = DefaultPrincipalAccessor.Instance; + PrincipalAccessor = principalAccessor; } - public IPrincipalAccessor PrincipalAccessor { get; set; } //TODO: Convert to constructor-injection + protected IPrincipalAccessor PrincipalAccessor { get; } - public virtual long? UserId + public override long? UserId { get { + if (OverridedValue != null) + { + return OverridedValue.UserId; + } + Claim userIdClaim = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userIdClaim?.Value)) { @@ -42,7 +50,7 @@ public virtual long? UserId } } - public virtual long? ImpersonatorUserId + public override long? ImpersonatorUserId { get { diff --git a/src/Stove/Runtime/Session/IStoveSession.cs b/src/Stove/Runtime/Session/IStoveSession.cs index 384057c..5ca5c2c 100644 --- a/src/Stove/Runtime/Session/IStoveSession.cs +++ b/src/Stove/Runtime/Session/IStoveSession.cs @@ -1,4 +1,6 @@ -namespace Stove.Runtime.Session +using System; + +namespace Stove.Runtime.Session { /// /// Defines some session information that can be useful for applications. @@ -16,5 +18,12 @@ public interface IStoveSession /// This is filled if a user is performing actions behalf of the . /// long? ImpersonatorUserId { get; } + + /// + /// Uses the specified user identifier. + /// + /// The user identifier. + /// + IDisposable Use(long? userId); } } diff --git a/src/Stove/Runtime/Session/NullStoveSession.cs b/src/Stove/Runtime/Session/NullStoveSession.cs index 2d13853..4f17d4a 100644 --- a/src/Stove/Runtime/Session/NullStoveSession.cs +++ b/src/Stove/Runtime/Session/NullStoveSession.cs @@ -1,11 +1,13 @@ -namespace Stove.Runtime.Session +using Stove.Runtime.Remoting; + +namespace Stove.Runtime.Session { /// /// Implements null object pattern for . /// - public class NullStoveSession : IStoveSession + public class NullStoveSession : StoveSessionBase { - private NullStoveSession() + private NullStoveSession() : base(new DataContextAmbientScopeProvider(new CallContextAmbientDataContext())) { } @@ -15,8 +17,8 @@ private NullStoveSession() public static NullStoveSession Instance { get; } = new NullStoveSession(); /// - public long? UserId => null; + public override long? UserId => null; - public long? ImpersonatorUserId => null; + public override long? ImpersonatorUserId => null; } } diff --git a/src/Stove/Runtime/Session/SessionOverride.cs b/src/Stove/Runtime/Session/SessionOverride.cs new file mode 100644 index 0000000..6691467 --- /dev/null +++ b/src/Stove/Runtime/Session/SessionOverride.cs @@ -0,0 +1,12 @@ +namespace Stove.Runtime.Session +{ + public class SessionOverride + { + public SessionOverride(long? userId) + { + UserId = userId; + } + + public long? UserId { get; } + } +} diff --git a/src/Stove/Runtime/Session/StoveSessionBase.cs b/src/Stove/Runtime/Session/StoveSessionBase.cs new file mode 100644 index 0000000..56546d7 --- /dev/null +++ b/src/Stove/Runtime/Session/StoveSessionBase.cs @@ -0,0 +1,27 @@ +using System; + +namespace Stove.Runtime.Session +{ + public abstract class StoveSessionBase : IStoveSession + { + public const string SessionOverrideContextKey = "Stove.Runtime.Session.Override"; + + protected StoveSessionBase(IAmbientScopeProvider sessionOverrideScopeProvider) + { + SessionOverrideScopeProvider = sessionOverrideScopeProvider; + } + + protected SessionOverride OverridedValue => SessionOverrideScopeProvider.GetValue(SessionOverrideContextKey); + + protected IAmbientScopeProvider SessionOverrideScopeProvider { get; } + + public abstract long? UserId { get; } + + public abstract long? ImpersonatorUserId { get; } + + public IDisposable Use(long? userId) + { + return SessionOverrideScopeProvider.BeginScope(SessionOverrideContextKey, new SessionOverride(userId)); + } + } +} diff --git a/src/Stove/StoveCoreRegistrationExtensions.cs b/src/Stove/StoveCoreRegistrationExtensions.cs index 1e304de..164f225 100644 --- a/src/Stove/StoveCoreRegistrationExtensions.cs +++ b/src/Stove/StoveCoreRegistrationExtensions.cs @@ -16,8 +16,10 @@ using Stove.MQ; using Stove.ObjectMapping; using Stove.Reflection; +using Stove.Runtime; using Stove.Runtime.Caching.Configuration; using Stove.Runtime.Caching.Memory; +using Stove.Runtime.Remoting; namespace Stove { @@ -174,7 +176,8 @@ private static void RegisterDefaults(IIocBuilder builder) .RegisterServices(r => r.Register(Lifetime.Singleton)) .RegisterServices(r => r.Register(Lifetime.Singleton)) .RegisterServices(r => r.Register(Lifetime.Singleton)) - .RegisterServices(r => r.Register(Lifetime.Singleton)); + .RegisterServices(r => r.Register(Lifetime.Singleton)) + .RegisterServices(r => r.RegisterGeneric(typeof(IAmbientScopeProvider<>), typeof(DataContextAmbientScopeProvider<>))); } } } diff --git a/src/Stove/project.json b/src/Stove/project.json index a611880..9296519 100644 --- a/src/Stove/project.json +++ b/src/Stove/project.json @@ -1,14 +1,14 @@ { - "version" : "0.0.8", + "version" : "0.0.9", "dependencies": { "Autofac": "4.3.0", - "Autofac.Extras.DynamicProxy": "4.1.0", "Autofac.Extras.IocManager.DynamicProxy": "2.0.7", "Castle.Core": "4.0.0", "FluentAssemblyScanner": "1.0.5", + "JetBrains.Annotations": "10.2.1", "Newtonsoft.Json": "9.0.1", - "Nito.AsyncEx": "3.0.1", + "Nito.AsyncEx": "4.0.1", "System.Collections.Immutable": "1.3.1" }, diff --git a/test/Stove.Demo.ConsoleApp/SomeDomainService.cs b/test/Stove.Demo.ConsoleApp/SomeDomainService.cs index 8775ead..2fff223 100644 --- a/test/Stove.Demo.ConsoleApp/SomeDomainService.cs +++ b/test/Stove.Demo.ConsoleApp/SomeDomainService.cs @@ -3,6 +3,8 @@ using Autofac.Extras.IocManager; +using LinqKit; + using MassTransit; using Stove.BackgroundJobs; @@ -14,6 +16,7 @@ using Stove.Domain.Repositories; using Stove.Domain.Uow; using Stove.EntityFramework.EntityFramework; +using Stove.EntityFramework.EntityFramework.Extensions; using Stove.Log; using Stove.Mapster.Mapster; using Stove.MQ; @@ -74,35 +77,58 @@ public void DoSomeStuff() _unitOfWorkManager.Current.SaveChanges(); - Person personCache = _cacheManager - .GetCache(DemoCacheName.Demo) - .Get("person", () => _personRepository.FirstOrDefault(x => x.Name == "Oğuzhan")); + Person personCache = _cacheManager.GetCache(DemoCacheName.Demo).Get("person", () => _personRepository.FirstOrDefault(x => x.Name == "Oğuzhan")); - //Person person = _personRepository.FirstOrDefault(x => x.Name == "Oğuzhan"); + Person person = _personRepository.FirstOrDefault(x => x.Name == "Oğuzhan"); Animal animal = _animalRepository.FirstOrDefault(x => x.Name == "Kuş"); - IEnumerable birds = _animalDapperRepository.GetSet(new { Name = "Kuş" }, 0, 10, "Id"); + #region DAPPER + + var list = new List + { + "elma", "armut" + }; + + ExpressionStarter predicate = PredicateBuilder.New(); + predicate.And(x => x.Name == "Kuş"); + + IEnumerable birdsSet = _animalDapperRepository.GetSet(new { Name = "Kuş" }, 0, 10, "Id"); IEnumerable personFromDapper = _personDapperRepository.GetList(new { Name = "Oğuzhan" }); - IEnumerable person2FromDapper = _personDapperRepository.Query("select * from Person with(nolock) where name =@name", new { name = "Oğuzhan" }); - Person person2Cache = _cacheManager - .GetCache(DemoCacheName.Demo) - .Get("person", () => _personRepository.FirstOrDefault(x => x.Name == "Oğuzhan")); + IEnumerable birdsFromExpression = _animalDapperRepository.GetSet(predicate, 0, 10, "Id"); + + IEnumerable birdsPagedFromExpression = _animalDapperRepository.GetListPaged(x => x.Name == "Kuş", 0, 10, "Name"); + + IEnumerable personFromDapperExpression = _personDapperRepository.GetList(x => x.Name.Contains("Oğuzhan")); - birds = birds.ToList(); + int birdCount = _animalDapperRepository.Count(x => x.Name == "Kuş"); var personAnimal = _animalDapperRepository.Query("select Name as PersonName,'Zürafa' as AnimalName from Person with(nolock) where name=@name", new { name = "Oğuzhan" }) .MapTo>(); + birdsFromExpression.ToList(); + birdsPagedFromExpression.ToList(); + birdsSet.ToList(); + + IEnumerable person2FromDapper = _personDapperRepository.Query("select * from Person with(nolock) where name =@name", new { name = "Oğuzhan" }); + + #endregion + + Person person2Cache = _cacheManager.GetCache(DemoCacheName.Demo).Get("person", () => _personRepository.FirstOrDefault(x => x.Name == "Oğuzhan")); + + Person oguzhan = _personRepository.Nolocking(persons => persons.FirstOrDefault(x => x.Name == "Oğuzhan")); + + Person oguzhan2 = _personRepository.FirstOrDefault(x => x.Name == "Oğuzhan"); + + uow.Complete(); + _messageBus.Publish(new PersonAddedMessage { Name = "Oğuzhan", CorrelationId = NewId.NextGuid() }); - uow.Complete(); - _hangfireBackgroundJobManager.EnqueueAsync(new SimpleBackgroundJobArgs { Message = "Oğuzhan" diff --git a/test/Stove.Demo.ConsoleApp/StoveDemoBootstrapper.cs b/test/Stove.Demo.ConsoleApp/StoveDemoBootstrapper.cs index 0fea4db..6ebb352 100644 --- a/test/Stove.Demo.ConsoleApp/StoveDemoBootstrapper.cs +++ b/test/Stove.Demo.ConsoleApp/StoveDemoBootstrapper.cs @@ -1,8 +1,10 @@ using System; +using System.Data.Entity.Infrastructure.Interception; using Stove.Bootstrapping; using Stove.Demo.ConsoleApp.DbContexes; using Stove.EntityFramework; +using Stove.EntityFramework.EntityFramework.Interceptors; using Stove.Hangfire; using Stove.Mapster; using Stove.RabbitMQ; @@ -28,6 +30,8 @@ public override void Start() { Configuration.TypedConnectionStrings.Add(typeof(AnimalStoveDbContext), "Default"); Configuration.TypedConnectionStrings.Add(typeof(PersonStoveDbContext), "Default"); + + DbInterception.Add(Configuration.Resolver.Resolve()); } } } diff --git a/test/Stove.Demo.ConsoleApp/project.json b/test/Stove.Demo.ConsoleApp/project.json index d448b15..50748b6 100644 --- a/test/Stove.Demo.ConsoleApp/project.json +++ b/test/Stove.Demo.ConsoleApp/project.json @@ -13,17 +13,18 @@ "FluentAssemblyScanner": "1.0.5", "Hangfire.Core": "1.6.8", "Hangfire.SqlServer": "1.6.8", - "MassTransit": "3.5.4", + "LinqKit": "1.1.8", + "MassTransit": "3.5.5", "Newtonsoft.Json": "9.0.1", "Owin": "1.0", - "Stove": "0.0.8", - "Stove.Dapper": "0.0.8-*", - "Stove.EntityFramework": "0.0.8-*", - "Stove.HangFire": "0.0.8-*", - "Stove.Mapster": "0.0.8-*", - "Stove.NLog": "0.0.8-*", + "Stove": "0.0.9", + "Stove.Dapper": "0.0.9-*", + "Stove.EntityFramework": "0.0.9-*", + "Stove.HangFire": "0.0.9-*", + "Stove.Mapster": "0.0.9-*", + "Stove.NLog": "0.0.9-*", "Stove.RabbitMQ": "1.0.0-*", - "Stove.Redis": "0.0.8-*" + "Stove.Redis": "0.0.9-*" }, "frameworks": { diff --git a/test/Stove.EntityFramework.Tests/project.json b/test/Stove.EntityFramework.Tests/project.json index 3bfb1bb..afb2c22 100644 --- a/test/Stove.EntityFramework.Tests/project.json +++ b/test/Stove.EntityFramework.Tests/project.json @@ -4,7 +4,7 @@ "testRunner": "xunit", "dependencies": { "Autofac": "4.3.0", - "Stove.EntityFramework": "0.0.8-*", + "Stove.EntityFramework": "0.0.9-*", "Stove.TestBase": "0.0.1-*" }, diff --git a/test/Stove.TestBase/ApplicationTestBase.cs b/test/Stove.TestBase/ApplicationTestBase.cs index 8200769..e737095 100644 --- a/test/Stove.TestBase/ApplicationTestBase.cs +++ b/test/Stove.TestBase/ApplicationTestBase.cs @@ -24,6 +24,10 @@ protected ApplicationTestBase() protected TestStoveSession TestStoveSession => LocalResolver.Resolve(); + protected override void PreBuild() + { + } + protected override void PostBuild() { } diff --git a/test/Stove.TestBase/TestStoveSession.cs b/test/Stove.TestBase/TestStoveSession.cs index ddd8020..71ce92f 100644 --- a/test/Stove.TestBase/TestStoveSession.cs +++ b/test/Stove.TestBase/TestStoveSession.cs @@ -1,13 +1,44 @@ -using Autofac.Extras.IocManager; +using System; +using Autofac.Extras.IocManager; + +using Stove.Runtime; using Stove.Runtime.Session; namespace Stove.TestBase { public class TestStoveSession : IStoveSession, ISingletonDependency { - public long? UserId { get; set; } + private readonly IAmbientScopeProvider _sessionOverrideScopeProvider; + + private long? _userId; + + public TestStoveSession(IAmbientScopeProvider sessionOverrideScopeProvider) + { + _sessionOverrideScopeProvider = sessionOverrideScopeProvider; + } + + public int? ImpersonatorTenantId { get; set; } + + public long? UserId + { + get + { + if (_sessionOverrideScopeProvider.GetValue(StoveSessionBase.SessionOverrideContextKey) != null) + { + return _sessionOverrideScopeProvider.GetValue(StoveSessionBase.SessionOverrideContextKey).UserId; + } + + return _userId; + } + set { _userId = value; } + } public long? ImpersonatorUserId { get; set; } + + public IDisposable Use(long? userId) + { + return _sessionOverrideScopeProvider.BeginScope(StoveSessionBase.SessionOverrideContextKey, new SessionOverride(userId)); + } } } diff --git a/test/Stove.TestBase/project.json b/test/Stove.TestBase/project.json index abc1732..4ead0c3 100644 --- a/test/Stove.TestBase/project.json +++ b/test/Stove.TestBase/project.json @@ -7,7 +7,7 @@ "xunit": "2.2.0-beta4-build3444", "NSubstitute": "1.10.0", "Shouldly": "2.8.2", - "Stove": "0.0.8" + "Stove": "0.0.9" }, "frameworks": { diff --git a/test/Stove.Tests.SampleApplication/Domain/Entities/Brand.cs b/test/Stove.Tests.SampleApplication/Domain/Entities/Brand.cs new file mode 100644 index 0000000..b3abea9 --- /dev/null +++ b/test/Stove.Tests.SampleApplication/Domain/Entities/Brand.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +using JetBrains.Annotations; + +using Stove.Domain.Entities; + +namespace Stove.Tests.SampleApplication.Domain.Entities +{ + [Table("Brand")] + public class Brand : Entity + { + private Brand() + { + } + + [Required] + [NotNull] + public virtual string Name { get; protected set; } + } +} diff --git a/test/Stove.Tests.SampleApplication/Domain/Entities/Cart.cs b/test/Stove.Tests.SampleApplication/Domain/Entities/Cart.cs deleted file mode 100644 index 008ea23..0000000 --- a/test/Stove.Tests.SampleApplication/Domain/Entities/Cart.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -using Stove.Domain.Entities; -using Stove.Domain.Entities.Auditing; - -namespace Stove.Tests.SampleApplication.Domain.Entities -{ - [Table(nameof(Cart))] - public class Cart : AggregateRoot, ICreationAudited - { - [Required] - public int ProductId { get; set; } - - [Required] - public DateTime CreationTime { get; set; } - - public long? CreatorUserId { get; set; } - } -} diff --git a/test/Stove.Tests.SampleApplication/Domain/Entities/Category.cs b/test/Stove.Tests.SampleApplication/Domain/Entities/Category.cs new file mode 100644 index 0000000..68709bf --- /dev/null +++ b/test/Stove.Tests.SampleApplication/Domain/Entities/Category.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +using Stove.Domain.Entities; + +namespace Stove.Tests.SampleApplication.Domain.Entities +{ + [Table("Category")] + public class Category : Entity + { + private Category() + { + } + + [Required] + public virtual string Name { get; protected set; } + } +} diff --git a/test/Stove.Tests.SampleApplication/Domain/Entities/Gender.cs b/test/Stove.Tests.SampleApplication/Domain/Entities/Gender.cs new file mode 100644 index 0000000..ba0626d --- /dev/null +++ b/test/Stove.Tests.SampleApplication/Domain/Entities/Gender.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +using Stove.Domain.Entities; + +namespace Stove.Tests.SampleApplication.Domain.Entities +{ + [Table("Gender")] + public class Gender : Entity + { + private Gender() + { + } + + [Required] + public virtual string Name { get; protected set; } + } +} diff --git a/test/Stove.Tests.SampleApplication/Domain/Entities/Order.cs b/test/Stove.Tests.SampleApplication/Domain/Entities/Order.cs deleted file mode 100644 index d5078f8..0000000 --- a/test/Stove.Tests.SampleApplication/Domain/Entities/Order.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; - -using Stove.Domain.Entities; -using Stove.Domain.Entities.Auditing; - -namespace Stove.Tests.SampleApplication.Domain.Entities -{ - [Table(nameof(Order))] - public class Order : AggregateRoot, ICreationAudited - { - public virtual ICollection OrderProducts { get; set; } - - public DateTime CreationTime { get; set; } - - public long? CreatorUserId { get; set; } - } -} diff --git a/test/Stove.Tests.SampleApplication/Domain/Entities/OrderDetail.cs b/test/Stove.Tests.SampleApplication/Domain/Entities/OrderDetail.cs deleted file mode 100644 index d62d4fd..0000000 --- a/test/Stove.Tests.SampleApplication/Domain/Entities/OrderDetail.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -using Stove.Domain.Entities; - -namespace Stove.Tests.SampleApplication.Domain.Entities -{ - [Table(nameof(OrderDetail))] - public class OrderDetail : Entity - { - [Required] - public virtual Order Order { get; set; } - - [Required] - public virtual Product Product { get; set; } - - [Required] - public virtual int Quantity { get; set; } - - [Required] - public virtual int Discount { get; set; } - - [Required] - public virtual int UnitPrice { get; set; } - } -} diff --git a/test/Stove.Tests.SampleApplication/Domain/Entities/Product.cs b/test/Stove.Tests.SampleApplication/Domain/Entities/Product.cs index 5608c7e..7fa3d14 100644 --- a/test/Stove.Tests.SampleApplication/Domain/Entities/Product.cs +++ b/test/Stove.Tests.SampleApplication/Domain/Entities/Product.cs @@ -1,29 +1,43 @@ -using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using JetBrains.Annotations; + using Stove.Domain.Entities; -using Stove.Domain.Entities.Auditing; namespace Stove.Tests.SampleApplication.Domain.Entities { - [Table(nameof(Product))] - public class Product : AggregateRoot, ICreationAudited + [Table("Product")] + public class Product : Entity { - [Required] - public virtual string Name { get; set; } + private Product() + { + _productCategories = new List(); + } [Required] - public virtual string Code { get; set; } + [NotNull] + public virtual string Name { get; protected set; } - [Required] - public virtual string Description { get; set; } + [NotNull] + [InverseProperty("Product")] + public virtual ProductBrand ProductBrand { get; protected set; } - public ProductDetail ProductDetail { get; set; } + [NotNull] + [InverseProperty("Product")] + public virtual ProductGender ProductGender { get; protected set; } - [Required] - public virtual DateTime CreationTime { get; set; } + [NotNull] + [InverseProperty("Product")] + public virtual ProductDetail ProductDetail { get; protected set; } + + [NotNull] + [ForeignKey("ProductId")] + public IReadOnlyCollection ProductCategories => _productCategories.ToImmutableList(); - public virtual long? CreatorUserId { get; set; } + [NotNull] + protected virtual ICollection _productCategories { get; set; } } } diff --git a/test/Stove.Tests.SampleApplication/Domain/Entities/ProductBrand.cs b/test/Stove.Tests.SampleApplication/Domain/Entities/ProductBrand.cs new file mode 100644 index 0000000..f5162e8 --- /dev/null +++ b/test/Stove.Tests.SampleApplication/Domain/Entities/ProductBrand.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +using JetBrains.Annotations; + +using Stove.Domain.Entities; + +namespace Stove.Tests.SampleApplication.Domain.Entities +{ + [Table("ProductBrand")] + public class ProductBrand : Entity + { + private ProductBrand() + { + } + + [NotNull] + [Required] + public virtual Product Product { get; protected set; } + public int ProductId { get; [UsedImplicitly] private set; } + + [NotNull] + [Required] + public virtual Brand Brand { get; protected set; } + public int BrandId { get; [UsedImplicitly] private set; } + } +} diff --git a/test/Stove.Tests.SampleApplication/Domain/Entities/ProductCategory.cs b/test/Stove.Tests.SampleApplication/Domain/Entities/ProductCategory.cs new file mode 100644 index 0000000..52091c0 --- /dev/null +++ b/test/Stove.Tests.SampleApplication/Domain/Entities/ProductCategory.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +using Stove.Domain.Entities; + +namespace Stove.Tests.SampleApplication.Domain.Entities +{ + [Table("ProductCategory")] + public class ProductCategory : Entity + { + private ProductCategory() + { + } + + [Required] + public virtual Category Category { get; protected set; } + + [Required] + public virtual Product Product { get; protected set; } + } +} diff --git a/test/Stove.Tests.SampleApplication/Domain/Entities/ProductDetail.cs b/test/Stove.Tests.SampleApplication/Domain/Entities/ProductDetail.cs index f2e87a8..5e89df0 100644 --- a/test/Stove.Tests.SampleApplication/Domain/Entities/ProductDetail.cs +++ b/test/Stove.Tests.SampleApplication/Domain/Entities/ProductDetail.cs @@ -1,16 +1,26 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using JetBrains.Annotations; + using Stove.Domain.Entities; namespace Stove.Tests.SampleApplication.Domain.Entities { - [Table(nameof(ProductDetail))] + [Table("ProductDetail")] public class ProductDetail : Entity { + private ProductDetail() + { + } + [Required] - public Product Product { get; set; } + [NotNull] + public virtual string Description { get; protected set; } - public virtual string Age { get; set; } + [Required] + [NotNull] + public virtual Product Product { get; protected set; } + public int ProductId { get; [UsedImplicitly] private set; } } } diff --git a/test/Stove.Tests.SampleApplication/Domain/Entities/ProductGender.cs b/test/Stove.Tests.SampleApplication/Domain/Entities/ProductGender.cs new file mode 100644 index 0000000..da474d8 --- /dev/null +++ b/test/Stove.Tests.SampleApplication/Domain/Entities/ProductGender.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +using JetBrains.Annotations; + +using Stove.Domain.Entities; + +namespace Stove.Tests.SampleApplication.Domain.Entities +{ + [Table("ProductGender")] + public class ProductGender : Entity + { + private ProductGender() + { + } + + [Required] + [NotNull] + public virtual Gender Gender { get; protected set; } + public int GenderId { get; [UsedImplicitly] private set; } + + [Required] + [NotNull] + public virtual Product Product { get; protected set; } + public int ProductId { get; [UsedImplicitly] private set; } + } +} diff --git a/test/Stove.Tests.SampleApplication/Domain/SampleApplicationDbContext.cs b/test/Stove.Tests.SampleApplication/Domain/SampleApplicationDbContext.cs index 0147c7b..d9ccb9e 100644 --- a/test/Stove.Tests.SampleApplication/Domain/SampleApplicationDbContext.cs +++ b/test/Stove.Tests.SampleApplication/Domain/SampleApplicationDbContext.cs @@ -1,6 +1,8 @@ using System.Data.Common; using System.Data.Entity; +using JetBrains.Annotations; + using Stove.EntityFramework.EntityFramework; using Stove.Tests.SampleApplication.Domain.Entities; @@ -17,14 +19,31 @@ public SampleApplicationDbContext(DbConnection connection) { } + [NotNull] public virtual IDbSet Products { get; set; } - public virtual IDbSet Users { get; set; } + [NotNull] + public virtual IDbSet Brands { get; set; } + + [NotNull] + public virtual IDbSet Categories { get; set; } + + [NotNull] + public virtual IDbSet Genders { get; set; } - public virtual IDbSet Orders { get; set; } + [NotNull] + public virtual IDbSet ProductBrands { get; set; } - public virtual IDbSet OrderDetails { get; set; } + [NotNull] + public virtual IDbSet ProductCategories { get; set; } + + [NotNull] + public virtual IDbSet ProductGenders { get; set; } + + [NotNull] + public virtual IDbSet Users { get; set; } + [NotNull] public virtual IDbSet ProductDetails { get; set; } } } diff --git a/test/Stove.Tests.SampleApplication/Session/Session_Tests.cs b/test/Stove.Tests.SampleApplication/Session/Session_Tests.cs new file mode 100644 index 0000000..5d68480 --- /dev/null +++ b/test/Stove.Tests.SampleApplication/Session/Session_Tests.cs @@ -0,0 +1,38 @@ +using Shouldly; + +using Stove.Runtime.Session; + +using Xunit; + +namespace Stove.Tests.SampleApplication.Session +{ + public class Session_Tests : SampleApplicationTestBase + { + private readonly IStoveSession _session; + + public Session_Tests() + { + Building(builder => { }).Ok(); + + _session = LocalResolver.Resolve(); + } + + [Fact] + public void Session_Override_Test() + { + _session.UserId.ShouldBeNull(); + + using (_session.Use(571)) + { + using (_session.Use(3)) + { + _session.UserId.ShouldBe(3); + } + + _session.UserId.ShouldBe(571); + } + + _session.UserId.ShouldBeNull(); + } + } +} diff --git a/test/Stove.Tests.SampleApplication/project.json b/test/Stove.Tests.SampleApplication/project.json index 1ac8e81..cd6e531 100644 --- a/test/Stove.Tests.SampleApplication/project.json +++ b/test/Stove.Tests.SampleApplication/project.json @@ -5,8 +5,8 @@ "dependencies": { "Effort.EF6": "1.3.0", - "Stove.EntityFramework": "0.0.8-*", - "Stove.Mapster": "0.0.8-*", + "Stove.EntityFramework": "0.0.9-*", + "Stove.Mapster": "0.0.9-*", "Stove.TestBase": "0.0.1-*" },