Linq to SQL с перехватом Unity - PullRequest
1 голос
/ 22 июня 2011

Я использую Linq-to-SQL с Unity в шаблоне репозитория. Я пытаюсь добавить перехватчик для защиты объекта в методе репозитория [Securable]IQueryable<TEntity> List<TEntity>(), который перехватывает вызов и возвращает только те объекты, на которые у пользователя есть права.

public class SecurableAttribute : HandlerAttribute
{...}

public class SecurableHandler : ICallHandler
{
    ...
    IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        var message = getNext()(input, getNext);
        var returnType = message.ReturnValue.GetType();
        if (typeof(IQueryable).IsAssignableFrom(returnType))
        {
            var entityType = returnType.GetGenericArguments().Single();
            var securableAttribute = entityType.GetAttribute<SecurableTypeAttribute>();
            if(securableAttribute != null)
            {
                //Build expression to filter the list from the attribute and primary key of the entity
                //Return the new IQueryable
            }
        }
        return message;
    }
}

Я построил выражение, но я не могу сделать message.ReturnValue.Where(expression), поскольку message.ReturnValue это object (message.ReturnValue на самом деле System.Data.Linq.Table<TEntity>, но я не хочу быть слишком привязанным к L2S ), и это во время выполнения, поэтому я не могу привести его обратно к универсальному и заменить message.ReturnValue.

Или я попробовал

public interface ISecurable<TKey>
{
    TKey SecurityId { get; }
}

на объекте, который немного меня блокирует, но я согласен с этим, если смогу отделить остальные аспекты безопасности. Это позволяет мне сделать следующее в IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext), где я строю вышеприведенное выражение:

if(typeof(ISecurableType).IsAssignableFrom(entityType))
{
    var secured = ((IQueryable<ISecurable>)message.ReturnValue).Where(expression);
    //Need to return secured as IQueryably<TEntity>
}

Теперь мне нужно привести secured к IQueryable<ISecurable>, но typeof(IQueryable<TEntity>).IsAssignableFrom(secured.GetType()) возвращает false, и выгрузка возвращаемого значения вызывает исключение, но, насколько я могу судить, оно работает с отложенным выполнением. (Кроме того, я не знаю TEntity во время разработки в SecurableHandler, но я знаю отраженный тип - но я попытался использовать объявление класса, которое, как я знаю, находится в тестировании.)

Есть ли способ как-нибудь изменить возвращаемые результаты? Я застрял, нуждаясь в возвращении непатентованного файла, который я не знаю во время разработки, что делает это невозможным, но я также не могу изменить выражение (((IQueryable)message.ReturnType).Expression объявлено как Expression Expression { get; }).

Есть ли какой-нибудь блеск, который мог бы указать мне, как это работает?

tl; dr Необходимо вернуть IQueryable<TEntity> во время выполнения от object, то есть Table<TEntity> : IQueryable<TEntity> с дополнительным .Where(expression).

1 Ответ

1 голос
/ 22 июня 2011

Вы можете попробовать создать динамическое выражение во время выполнения. Вам не нужно явно приводить IQueryable обратно к его универсальному типу, если вы не изменяете типы элементов с помощью «Select».

Пример:

    public class SecurityHandler : ICallHandler
{
    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        var message = getNext()(input, getNext);
        var returnType = message.ReturnValue.GetType();
        if (typeof(IQueryable).IsAssignableFrom(returnType))
        {
            var entityType = returnType.GetGenericArguments().Single();

            var securableAttribute = entityType.GetAttribute<SecurableTypeAttribute>();
            if (securableAttribute != null)
            {
                //Build expression to filter the list from the attribute and primary key of the entity
                //Return the new IQueryable
                message.ReturnValue = AddWhereExpression(
                    (IQueryable)message.ReturnValue, 
                    securableAttribute.FilterValues,
                    securableAttribute.FilterPropertyName);
            }
        }
        return message;
    }

    public int Order { get; set; }

    private static IQueryable AddWhereExpression(IQueryable query, IEnumerable ids, string filterPropertyName)
    {
        // Build this expression:
        // item => ids.Contains(item.[PrimaryKeyPropertyName])

        var itemParameter = Expression.Parameter(query.ElementType, "item");

        var itemParameterProperty = Expression.Property(itemParameter, filterPropertyName);

        var listParameter = Expression.Constant(ids);

        var containsExpression = Expression.Call(
            typeof(System.Linq.Enumerable),
            "Contains",
            new[] { typeof(int) },
            listParameter,
            itemParameterProperty);

        var delegateTypeExpression = Expression.GetFuncType(new[] { query.ElementType, typeof(bool) });

        var whereExpression = Expression.Lambda(
            delegateTypeExpression,
            containsExpression,
            new[] { itemParameter }
            );

        Expression callWhere = Expression.Call(
                                     typeof(Queryable),
                                     "Where",
                                     new Type[] { query.ElementType },  // type args for Where<T>()
                                     query.Expression,
                                     whereExpression
                                     );

        return query.Provider.CreateQuery(callWhere);
    }
}

Я предполагаю, что ваш атрибут предоставит массив допустимых значений.

Вот несколько методов расширения, которые помогут с этим процессом:

public static class TypeExtensions
{       

    public static TAttribute GetAttribute<TAttribute>(this Type type)
    {
        var attributes = type.GetCustomAttributes(typeof(TAttribute), true);
        if (attributes.Length == 0) return default(TAttribute);
        return (TAttribute)attributes[0];
    }      

    public static PropertyInfo GetPropertyWithAttributeValue<TAttribute>(
        this IEnumerable<PropertyInfo> properties,
        Func<TAttribute, bool> findPredicate)
        where TAttribute : Attribute
    {
        var property = from p in properties
                       where p.HasAttribute<TAttribute>() &&
                       findPredicate.Invoke(p.GetAttribute<TAttribute>())
                       select p;

        return property.FirstOrDefault();
    }

    public static bool HasAttribute<TAttribute>(this PropertyInfo propertyInfo)
    {
        return propertyInfo.GetCustomAttributes(typeof(TAttribute), true).Any();
    }

    public static TAttribute GetAttribute<TAttribute>(this PropertyInfo propertyInfo)
    {
        var attributes = propertyInfo.GetCustomAttributes(typeof(TAttribute), true);
        if (attributes.Length == 0) return default(TAttribute);
        return (TAttribute)attributes[0];
    }
}

Я сам не пробовал запускать это, но, надеюсь, этого достаточно, чтобы вы начали.

...