在评估IQueryable之前指定延迟操作

时间:2015-02-25 12:37:45

标签: c# linq iqueryable

我想对从IQueryable返回的实体执行一些后处理,但我想在评估查询之前指定后处理。例如:

IQueryable<Client> GetClients()
{
    return _context.Set<Client>()
        .PostProcess(c => c.MobileNumber = c.MobileNumber.Replace(" ", ""));
}

Client GetClient(int id)
{
    return GetClients()
        .FirstOrDefault(c => c.Id == id);
}

在上面的示例中,我希望从上述两种方法返回的每个Client都可以从其移动号码中删除所有空格。这可能吗?

1 个答案:

答案 0 :(得分:4)

编辑:这是一个坏主意

它适用于OP中所述的简单情况,但任何进一步的IQueryable扩展(例如.Where()子句)都将丢弃后处理行为。我不确定在技术上可以做我想做的事情,现在我开始想到它我甚至不确定语义将超出简单的情况。尽管如此,这个简单的案例仍然有效,并且能够让事情变得流畅......


原件:

我想我会分享我的方法以防万一有人发现它有用(或评论为什么这是一个坏主意)。该解决方案使用两个装饰器来推迟后处理操作,直到执行查询,并使用扩展方法来设置它们:

public static class QueryableExtensions
{
    public static IQueryable<T> PostProcess<T>(this IQueryable<T> source, Action<T> postProcessor) where T : class
    {
        return new QueryableWrapper<T>(source, postProcessor);
    }

    // wraps IQueryProvider.Execute methods with post-processing action
    class QueryProviderWrapper<T> : IQueryProvider where T : class
    {
        private readonly IQueryProvider _wrapped;
        private readonly Action<T> _postProcessor;

        public QueryProviderWrapper(IQueryProvider wrapped, Action<T> postProcessor)
        {
            _wrapped = wrapped;
            _postProcessor = postProcessor;
        }

        public IQueryable CreateQuery(Expression expression)
        {
            return _wrapped.CreateQuery(expression);
        }

        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
        {
            return _wrapped.CreateQuery<TElement>(expression);
        }

        public object Execute(Expression expression)
        {
            var result = _wrapped.Execute(expression);
            var asT = result as T;
            if (asT != null)
                _postProcessor(asT);
            return result;
        }

        public TResult Execute<TResult>(Expression expression)
        {
            var result = _wrapped.Execute<TResult>(expression);
            var asT = result as T;
            if (asT != null)
                _postProcessor(asT);
            return result;
        }
    }

    // wraps IQueryable.GetEnumerator() with post-processing action
    class QueryableWrapper<T> : IQueryable<T> where T : class
    {
        private readonly IQueryable<T> _wrapped;
        private readonly Action<T> _postProcessor;
        private readonly IQueryProvider _provider;

        public QueryableWrapper(IQueryable<T> wrapped, Action<T> postProcessor)
        {
            _wrapped = wrapped;
            _postProcessor = postProcessor;
            _provider = new QueryProviderWrapper<T>(_wrapped.Provider, postProcessor);
        }

        public Expression Expression
        {
            get { return _wrapped.Expression; }
        }

        public Type ElementType
        {
            get { return _wrapped.ElementType; }
        }

        public IQueryProvider Provider
        {
            get { return _provider; }
        }

        public IEnumerator<T> GetEnumerator()
        {
            return _wrapped
                .AsEnumerable()
                .Do(_postProcessor)
                .GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

GetEnumerator()装饰器使用Interactive Extensions中的.Do()扩展方法,就像SelectForEach之间的交叉:它被懒惰地调用,但需要Action<T>并返回T