2
votes

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;
}
1

1 Answers

2
votes

Wow, I never seem to get that dynamic linq has seemingly no limits but here we are again.

Here's what I came up with ...

public override IQueryable<T> GetAll()
{
    var result = base.GetAll();

    if (typeof(IHaveRoles).IsAssignableFrom(typeof(T)) && !AuthInfo.SignatureIsValid)
        result = result.Where("Roles.Any(Users.Any(Id == @0))", User.Id);

    return result;
}

because dynamic is evaluated at runtime the same design time rules don't apply, meaning I can apply the exact same linq I wanted (with a little bit of an overhead cost of course).