InvalidOperationException: variable 'x' of type 'X' referenced from scope '', but it is not defined
icnocop opened this issue · 4 comments
Hi.
Thank you for DynamicFilters.
DynamicFilters works without issues for one of my simple cases, but it throws an exception when trying to make it work for a little more complex case, so I'm not sure if DynamicFilters supports what I'm trying to do.
Basically, I'm trying to filter Group
s from a User
based on the User
's permissions.
A User
can be authorized for multiple Group
s.
A Group
can be authorized to multiple User
s.
This is a basic many-to-many relationship.
The complexity comes when I introduce permission inheritance.
In this case a Group
can have a parent Group
.
If a user is authorized for a Group
, it is also authorized for its children.
.NET Framework 4.5.1
EntityFramework 6.1.3
EntityFramework.DynamicFilters 2.11.0-beta2
MyDatabaseContext.cs
using EntityFramework.DynamicFilters;
using System.Data.Entity;
using System.Linq;
using System.Threading;
namespace DynamicFiltersTest
{
public class MyDatabaseContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Group> Groups { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Group>()
.HasMany(e => e.Users)
.WithMany(e => e.Groups)
.Map(m => m.ToTable("UserGroups").MapLeftKey("GroupId").MapRightKey("UserId"));
// the following filter works without issues, but doesn't check permission inheritance
//modelBuilder.Filter(
// "UserGroupsFilter",
// (Group g, long? userId) => g.Users.Any(x => x.UserId == userId),
// (MyDatabaseContext context) => GetUserId(context));
// the following filter causes the following exception:
// System.InvalidOperationException: variable 'g' of type 'DynamicFiltersTest.Group' referenced from scope '', but it is not defined
modelBuilder.Filter(
"UserGroupsFilter",
(Group g, long? userId) => IsUserIdAuthorized(g, userId),
(MyDatabaseContext context) => GetUserId(context));
}
private bool IsUserIdAuthorized(Group group, long? userId)
{
if ((group == null) || (userId == null))
{
return false;
}
if (group.Users.Any(x => x.UserId == userId))
{
return true;
}
// check parent
return IsUserIdAuthorized(group.Parent, userId);
}
private long? GetUserId(MyDatabaseContext context)
{
return context.Users.SingleOrDefault(x => x.Name == Thread.CurrentPrincipal.Identity.Name)?.UserId;
}
}
}
User.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace DynamicFiltersTest
{
public class User
{
public User()
{
Groups = new HashSet<Group>();
}
[Key]
public long UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
}
Group.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DynamicFiltersTest
{
public class Group
{
public Group()
{
Users = new HashSet<User>();
}
[Key]
public long GroupId { get; set; }
public string Name { get; set; }
public long? ParentGroupId { get; set; }
[ForeignKey(nameof(ParentGroupId))]
public virtual Group Parent { get; set; }
public virtual ICollection<User> Users { get; set; }
}
}
UnitTest1.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
using System.Collections.Generic;
namespace DynamicFiltersTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
using (MyDatabaseContext context = new MyDatabaseContext())
{
List<Group> groupsList1 = context.Groups.ToList(); // the exception is thrown here
}
}
}
}
Any advice?
Thank you!
Hello @icnocop ,
One problem I see is you try to filter with code in your application.
The filter normally append some filtering SQL to the statement that will be created.
In your case, unless I'm wrong, the logic used to filter is currently a recursive function. There is no way to convert it into a SQL to allow some filtering.
What I tried is something similar to this
modelBuilder.Filter("UserGroupsFilter", (Group g, long? userId) =>
userId.HasValue && g.Users.Any(x => x.UserId == userId) ||
g.Parent.Users.Any(y => y.UserId == userId), (int?)null);
However, that currently throw a Stack Overflow error when adding the Users.Any
part.
I believe it may be possible to fix the Stack Overflow, by example, by commenting a line (that will have some side effect but at least doesn't throw a Stack Overflow error,
The following code return the group 1, 2, 3 as expected.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Data.Entity;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using EntityFramework.DynamicFilters;
namespace Z.Lab
{
public partial class Form_Request_ManyToMany : Form
{
public Form_Request_ManyToMany()
{
InitializeComponent();
// CLEAR
using (var ctx = new EntityContext())
{
ctx.Groups.RemoveRange(ctx.Groups);
ctx.Users.RemoveRange(ctx.Users);
ctx.SaveChanges();
}
// SEED
using (var ctx = new EntityContext())
{
var group1 = ctx.Groups.Add(new Group() { Name = "Group_1" });
var group2 = ctx.Groups.Add(new Group() { Name = "Group_2" });
var group3 = ctx.Groups.Add(new Group() { Name = "Group_3" });
var group4 = ctx.Groups.Add(new Group() { Name = "Group_4" });
var user1 = ctx.Users.Add(new User() {Name = "User_1"});
group1.Users = new List<User>() {user1};
group2.Parent = group3;
group3.Users = new List<User>() { user1 };
ctx.SaveChanges();
}
// TEST
using (var ctx = new EntityContext())
{
var list = ctx.Groups.ToList();
}
}
public class EntityContext : DbContext
{
public EntityContext() : base("CodeFirstEntities")
{
}
public DbSet<Group> Groups { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//modelBuilder.Filter("UserGroupsFilter", (Group g, long? userId) =>
// userId.HasValue && g.Users.Union(g.Parent.Users).Any(x => x.UserId == userId), (int?)null);
modelBuilder.Filter("UserGroupsFilter", (Group g, long? userId) =>
userId.HasValue && g.Users.Any(x => x.UserId == userId) ||
g.Parent.Users.Any(y => y.UserId == userId), 1);
//modelBuilder.Filter("UserGroupsFilter", (Group g, long? userId) =>
// userId.HasValue && g.Users.Any(x => x.UserId == userId), (int?)null);
modelBuilder.Types().Configure(x => x.ToTable(GetType().DeclaringType != null ? GetType().DeclaringType.FullName.Replace(".", "_") + "_" + x.ClrType.Name : ""));
base.OnModelCreating(modelBuilder);
}
}
public class Group
{
public Group()
{
Users = new HashSet<User>();
}
[Key]
public long GroupId { get; set; }
public string Name { get; set; }
public long? ParentGroupId { get; set; }
[ForeignKey(nameof(ParentGroupId))]
public virtual Group Parent { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
public User()
{
Groups = new HashSet<Group>();
}
[Key]
public long UserId { get; set; }
public string Name { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
}
}
Let me know if the filter provided could work in your scenario.
If you confirm me, I will try to work on the Stack Overflow issue during the weekend.
Best Regards,
Jonathan
Hi @JonathanMagnan.
Thank you for the follow up.
Unfortunately, in my case, the inheritance tree of Group
s and their Parent
s may consist of more than one hierarchical level, so just checking the Parent
in your query wouldn't work for my scenario.
It now seems like I have to perform the filter on objects rather than on the Entity Framework layer, after the SQL has been executed.
Thank you!
Yup, this is what I thought :(
Can we close this request?
Best Regards,
Jonathan