Open mwinner313 opened 7 years ago
First of all, I'm not sure what the GlobalFilter class or ApplyFilter method are. So I'm going to assume GlobalFilter is derived from DbContext and the ApplyFilter method is called from inside the OnModelCreating() method.
I also don't know the type of the 'AllowedRolesPremissionJsonString' property. From the name of it, it sounds like it's a string. But you are referencing a ".Contains" property. If it is a string, then that will be a problem since you are passing a reference to the Contains method when the .Any() is going to be expecting a string.
Also, you can't access a private method to retrieve the value for the "roles" parameter. When the expression value is needed, it will fail to evaluate that because it will not have a reference to the "GlobalFilters" instance. The proper way to handle that is to reference your DbContext derived class as described here: https://github.com/jcachat/EntityFramework.DynamicFilters#parameter-expressions.
So based on my assumptions above, I THINK your filter should look like this:
modelBuilder.Filter("RestrictItemsBaseOnAccsess",
(IAccessRestrictedItem x, List<string> roles) => x.AccessType == AccessType.Public
|| roles.Any(r => r == x.AllowedRolesPremissionJsonString),
(GlobalFilter ctx) => ctx.IdentityManager.GetCurrentIdentityRoles());
GlobalFilter is a base class that i will inject a list from it to my dbcontext from its constructor
public class AppDbContext : IdentityDbContext<User, Role, Guid, UserLogin, UserRole, UserClaim>, IUnitOfWork
{
private readonly List<GlobalFilter> _globalFilters;
public AppDbContext(List<GlobalFilter> globalFilters = null) : base("name=DefaultConnection")
{
_globalFilters = globalFilters;
Database.SetInitializer(new MigrateDatabaseToLatestVersion<AppDbContext, Configuration>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add<MapProtectedFieldConvention>();
modelBuilder.Configurations.AddFromAssembly(typeof(User).Assembly);
modelBuilder.Ignore<BaseEntity>();
FindAndRegisterEntitiesFromAssembly(typeof(BaseEntity).Assembly, typeof(DbEntity), modelBuilder);
_globalFilters?.ForEach(x => x.ApplyFilter(modelBuilder));
base.OnModelCreating(modelBuilder);
}
}
And any drived class applies its own logic and AllowedRolesPremissionJsonString property is a string field that stores json data like this
public ObservableCollection<RolePremission> AllowedRolesPermissions
{
get
{
var additionalPermissions =
JsonConvert.DeserializeObject<ObservableCollection<RolePremission>>(AllowedRolesPremissionJsonString ?? string.Empty)
?? new ObservableCollection<RolePremission>();
additionalPermissions.CollectionChanged += SaveToPremissinBackingField;
return additionalPermissions;
}
set { AllowedRolesPremissionJsonString = JsonConvert.SerializeObject(value); }
}
private void SaveToPremissinBackingField(object sender, NotifyCollectionChangedEventArgs e)
{
var premissions = sender as ObservableCollection<RolePremission>;
if (premissions == null) throw new InvalidOperationException("Cant Observe Entitiy AllowedPremissions For DataPresistence");
this.AllowedRolesPermissions = premissions;
}
thanks .
any way i need a way to decouple my db context from system requirement logics as the application grows dbcontext filters conut grows too and i tried to do some thing like entitytype configuratuion class does, decoupling Entiry filter configuration, i wish there was some thing like this
SomeEntiry: EntiryFilterConfiguration<interface or class>
{
public SomeEntiry()
{
HasFilter(...)
}
}
i am not good in english sorry about Misspellings and grammar thank you again.
modelBuilder.Filter("RestrictItemsBaseOnAccsess",
(IAccessRestrictedItem x, List<string> roles) => x.AccessType == AccessType.Public
|| roles.Any(r=>r== x.AllowedRolesPremissionJsonString),
(AppDbContext ctx)=> ctx.CurrentUserRoles);
i wrote this for sake of testing but the same problem exists : DbExpressionBinding requires an input expression with a collection ResultType. r== x.AllowedRolesPremissionJsonString is not applicable to my code but i wrote that for test i think problem comes from (roles.Any)
example of AllowedRolesPremissionJsonString value :: [ {"RoleName":"admin","AccessPremission":2}, {"RoleName":"author","AccessPremission":2} ] // 2 means just can see, comes from an enum because of that i used contains method and also it test something else
modelBuilder.Filter("RestrictItemsBaseOnAccsess",
(IAccessRestrictedItem x, List<string> roles) => x.AccessType == AccessType.Public
|| x.AllowedRolesPremissionJsonString.Contains("hello"),
GetIdentityroles);
and what i got is ::
Test method VarinaCmsV2.Data.Tests.AppDbContextPRoxyTests.ShouldFilterBaseOnRestrictedAccses threw exception:
System.Data.Entity.Core.EntityCommandCompilationException: An error occurred while preparing the command definition. See the inner exception for details. ---> System.NotSupportedException: Unsupported object type used in Contains() - type = PropertyExpression
at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.MapContainsExpression(MethodCallExpression node)
at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.VisitBinary(BinaryExpression node)
at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.Convert(DynamicFilterDefinition filter, DbExpressionBinding binding, DbContext dbContext, DataSpace dataSpace)
at EntityFramework.DynamicFilters.DynamicFilterQueryVisitorCSpace.BuildFilterExpressionWithDynamicFilters(IEnumerable`1 filterList, DbExpressionBinding binding, DbExpression predicate)
at EntityFramework.DynamicFilters.DynamicFilterQueryVisitorCSpace.Visit(DbScanExpression expression)
at System.Data.Entity.Core.Common.CommandTrees.DbScanExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.VisitExpression(DbExpression expression)
at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.VisitExpressionBinding(DbExpressionBinding binding)
at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.VisitExpressionBindingEnterScope(DbExpressionBinding binding)
at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.Visit(DbProjectExpression expression)
at EntityFramework.DynamicFilters.DynamicFilterQueryVisitorCSpace.Visit(DbProjectExpression expression)
at System.Data.Entity.Core.Common.CommandTrees.DbProjectExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
at EntityFramework.DynamicFilters.DynamicFilterInterceptor.TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
at System.Data.Entity.Infrastructure.Interception.DbCommandTreeDispatcher.<Created>b__0(IDbCommandTreeInterceptor i, DbCommandTreeInterceptionContext c)
at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TInterceptionContext,TResult](TResult result, TInterceptionContext interceptionContext, Action`2 intercept)
at System.Data.Entity.Infrastructure.Interception.DbCommandTreeDispatcher.Created(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
at System.Data.Entity.Core.Common.DbProviderServices.CreateCommandDefinition(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.CreateCommandDefinition(ObjectContext context, DbQueryCommandTree tree)
--- End of inner exception stack trace ---
at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.CreateCommandDefinition(ObjectContext context, DbQueryCommandTree tree)
at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.Prepare(ObjectContext context, DbQueryCommandTree tree, Type elementType, MergeOption mergeOption, Boolean streaming, Span span, IEnumerable`1 compiledQueryParameters, AliasGenerator aliasGenerator)
at System.Data.Entity.Core.Objects.EntitySqlQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__6()
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__5()
at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)
at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<System.Collections.Generic.IEnumerable<T>.GetEnumerator>b__0()
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at VarinaCmsV2.Data.Tests.AppDbContextPRoxyTests.ShouldFilterBaseOnRestrictedAccses() in E:\programming\projects\Source\VarinaCmsV2\VarinaCmsV2.Data.Tests\AppDbContextPRoxyTests.cs:line 38
any idea ?? i still have that problem yeah i know its alot of explanations :D
I don't know what's wrong with the .Any() clause. That is supported. Look in the unit tests at the ChildCollectionFiltersTests.cs. There are a couple of tests there for .Any(). Maybe experiment with those and try to plug in your specific entities (as much as possible) to see if you can reproduce it there.
string.Contains() is not supported at the moment. That only works on an IEnumerable. That is something I can fix but that will only fix your .Contains("Hello") attempt.
Also, I think I see what you were trying to do in your original filter. Where you have this clause:
roles.Any(x.AllowedRolesPremissionJsonString.Contains)
I don't think that syntax can be supported and might be why it's complaining about needing a collection result type.
But once I add support for stirng.Contains() in the filter linq, this might work:
roles.Any(r => x.AllowedRolesPremissionJsonString.Contains(r))
I'll try to get that done in the next day or 2.
I just pushed out an update (v 2.10.0) that adds support for string.Contains().
But, after another look, I don't think this is going to work at all. If you create the filter like this:
roles.Any(r => x.AllowedRolesPremissionJsonString.Contains(r))
that will create a subquery that attempts to evaluate this portion of that query:
r => x.AllowedRolesPremissionJsonString.Contains(r)
The problem there is that it's accessing the outer table ('x' in the linq query) which would not be valid sql since the inner sql query will not have any reference to the outer "x" entity set.
I also don't know if a valid query can be created against the parameter "roles" - it may only work on an entity collection.
Can you add support for filter with claim
ex message: