Ok I have a ton of questions asked of this nature on here but none that actually solve this problem either fully or correctly.
Lets assume I have the following code ...
public interface IHaveRoles {
ICollection<Role> Roles { get;set; }
}
public class Foo : IHaveRoles {
public ICollection<Role> Roles { get;set; }
}
public class Bar { }
... then I have method like this ...
public override IQueryable<T> GetAll()
{
return base.GetAll();
}
in to that method I want to add a check that may result in me dynamically adding a simple where clause to my IQueryable ...
if(typeof(IHaveRoles<Role>).IsAssignableFrom(typeof(T))) {
return base.GetAll()
.Where(i => i.Roles.Any(r => r.Users.Any(u => u.Id == User.Id)));
}
... It's a simple enough where clause in it's own right and if I knew what T was at design time then this would be a non issue.
However, casting result
to an IQueryable<IHaveRoles>
is not an option because when i'm done appending my clause I can no longer cast it back to an IQueryable<T>
as IHaveRoles
is not a sub type of T
So how do we solve this problem whilst retaining the ability to return an IQueryable<T>
and without illegal casts as shown in some of the answers given in other questions like these ...
Cast Entity to Implemented Interface in a Generic Method Using LINQ for Entity Framework
LINQ-to-entities casting issue
... and in a way that avoids the problem with EF not supporting not EDM primitive types ...
LINQ to Entities only supports casting EDM primitive or enumeration types with IEntity interface
EDIT: Some tested implementations ...
public override IQueryable<T> GetAll()
{
var result = base.GetAll();
if (typeof(IHaveRoles).IsAssignableFrom(typeof(T)) && !AuthInfo.SignatureIsValid)
{
// tried implementations that don't work ...
// InvalidCastException (CLR can't cast an IQueryable<IHaveRoles> to a IQueryable<T>
var queryableRoleSecured = ((IQueryable<IHaveRoles>)result);
result = (IQueryable<T>)queryableRoleSecured
.Where(i => i.Roles.Any(r => User.Roles.Contains(r)));
// NotSupportedException (EF won't accept this kind of casting)
result = result
.Where(i => ((IHaveRoles)i).Roles.Any(r => r.Users.Any(u => u.Id == User.Id)));
}
return result;
}