gpt4 book ai didi

linq - 用于包装异常的 IQueryable

转载 作者:行者123 更新时间:2023-12-01 14:49:58 27 4
gpt4 key购买 nike

我最担心暴露一个 IQueryable在我的业务逻辑中,它可能会在我的业务逻辑中引发 Entity Framework 异常。我认为这是一个问题,因为我的业务层要么需要知道我正在使用 Entity Framework - 要么 - 我必须捕获一个非常通用的异常。

相反,我想创建一个 IQueryable捕获 Entity Framework 异常并将它们转换为我的数据层异常类型。

最终,我希望我的代码如下所示:

public IQueryable<Customer> GetCustomers()
{
var customers = from customer in dbContext.Customers
where customer.IsActive
select customer;
return customers.WrapErrors(ex => new DataLayerException("oops", ex);
}

然后客户端将能够添加额外的 LINQ 子句。如果发生错误(数据库宕机),原始异常将与 DataLayerException 一起包装。

最佳答案

@Moho 的答案的问题在于它取代了基础 IQueryable .当您简单地包装 IQueryable 时,它会影响最终的 Expression即生成。如果你立即包装ISet<T> ,它将中断对 Include 的调用.此外,它可以影响其他操作发生的方式/时间。所以解决方案实际上有点复杂。

在寻找解决方案的过程中,我看到了这个博客:http://blogs.msdn.com/b/alexj/archive/2010/03/01/tip-55-how-to-extend-an-iqueryable-by-wrapping-it.aspx .不幸的是,这个例子有点坏,但很容易修复(和改进)。下面,我发布了我最终编写的代码。

第一个类是一个抽象基类,可以创建不同类型的IQueryable wrapper 。 LINQ 使用 IQueryProvider s 将 LINQ 表达式转换为可执行代码。我创建了一个 IQueryProvider它只是将调用传递给底层提供者,使其基本上不可见。

public abstract class InterceptingProvider : IQueryProvider
{
private readonly IQueryProvider provider;

protected InterceptingProvider(IQueryProvider provider)
{
this.provider = provider;
}

public virtual IEnumerator<TElement> ExecuteQuery<TElement>(Expression expression)
{
IQueryable<TElement> query = provider.CreateQuery<TElement>(expression);
IEnumerator<TElement> enumerator = query.GetEnumerator();
return enumerator;
}

public virtual IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
IQueryable<TElement> queryable = provider.CreateQuery<TElement>(expression);
return new InterceptingQuery<TElement>(queryable, this);
}

public virtual IQueryable CreateQuery(Expression expression)
{
IQueryable queryable = provider.CreateQuery(expression);
Type elementType = queryable.ElementType;
Type queryType = typeof(InterceptingQuery<>).MakeGenericType(elementType);
return (IQueryable)Activator.CreateInstance(queryType, queryable, this);
}

public virtual TResult Execute<TResult>(Expression expression)
{
return provider.Execute<TResult>(expression);
}

public virtual object Execute(Expression expression)
{
return provider.Execute(expression);
}
}

然后我创建了一个类来包装实际的 IQuerable .此类将任何调用发送到提供程序。这样调用 Where , Select等被传递给底层提供者。
internal class InterceptingQuery<TElement> : IQueryable<TElement>
{
private readonly IQueryable queryable;
private readonly InterceptingProvider provider;

public InterceptingQuery(IQueryable queryable, InterceptingProvider provider)
{
this.queryable = queryable;
this.provider = provider;
}

public IQueryable<TElement> Include(string path)
{
return new InterceptingQuery<TElement>(queryable.Include(path), provider);
}

public IEnumerator<TElement> GetEnumerator()
{
Expression expression = queryable.Expression;
return provider.ExecuteQuery<TElement>(expression);
}

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

public Type ElementType
{
get { return typeof(TElement); }
}

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

public IQueryProvider Provider
{
get { return provider; }
}
}

请注意,该类实现了一个名为 Include 的方法。 .这允许 System.Data.Entity.QueryableExtensions.Include针对包装器工作的方法。

此时,我们只需要一个 InterceptingProvider 的子类实际上可以包装抛出的异常。
internal class WrappedProvider<TException> : InterceptingProvider
where TException : Exception
{
private readonly Func<TException, Exception> wrapper;

internal WrappedProvider(IQueryProvider provider, Func<TException, Exception> wrapper)
: base(provider)
{
this.wrapper = wrapper;
}

public override IEnumerator<TElement> ExecuteQuery<TElement>(Expression expression)
{
return Check(() => wrapEnumerator<TElement>(expression), wrapper);
}

private IEnumerator<TElement> wrapEnumerator<TElement>(Expression expression)
{
IEnumerator<TElement> enumerator = base.ExecuteQuery<TElement>(expression);
return new WrappedEnumerator<TElement>(enumerator, wrapper);
}

public override TResult Execute<TResult>(Expression expression)
{
return Check(() => base.Execute<TResult>(expression), wrapper);
}

public override object Execute(Expression expression)
{
return Check(() => base.Execute(expression), wrapper);
}

internal static TResult Check<TResult>(Func<TResult> action, Func<TException, Exception> wrapper)
{
try
{
return action();
}
catch (TException exception)
{
throw wrapper(exception);
}
}

private class WrappedEnumerator<TElement> : IEnumerator<TElement>
{
private readonly IEnumerator<TElement> enumerator;
private readonly Func<TException, Exception> wrapper;

public WrappedEnumerator(IEnumerator<TElement> enumerator, Func<TException, Exception> wrapper)
{
this.enumerator = enumerator;
this.wrapper = wrapper;
}

public TElement Current
{
get { return enumerator.Current; }
}

public void Dispose()
{
enumerator.Dispose();
}

object IEnumerator.Current
{
get { return Current; }
}

public bool MoveNext()
{
return WrappedProvider<TException>.Check(enumerator.MoveNext, wrapper);
}

public void Reset()
{
enumerator.Reset();
}
}
}

在这里,我只是覆盖了 ExecuteQueryExecute方法。在 Execute的情况下,底层提供程序立即执行,我捕获并包装任何异常。至于 ExecuteQuery , 我创建了一个 IEnumerator 的实现按照@Moho 的建议包装异常。

唯一缺少的是实际创建 WrappedProvider 的代码。 .我创建了一个简单的扩展方法。
public static class QueryWrappers
{
public static IQueryable<TElement> Handle<TElement, TException>(this IQueryable<TElement> source, Func<TException, Exception> wrapper)
where TException : Exception
{
return WrappedProvider<TException>.Check(() => handle(source, wrapper), wrapper);
}

private static IQueryable<TElement> handle<TElement, TException>(IQueryable<TElement> source, Func<TException, Exception> wrapper)
where TException : Exception
{
var provider = new WrappedProvider<TException>(source.Provider, wrapper);
return provider.CreateQuery<TElement>(source.Expression);
}
}

我在几个场景中测试了这段代码,看看我是否可以破坏某些东西:SQL Server 关闭; Single在具有多条记录的表上; Include -ing 一个不存在的表;等等。它似乎在每种情况下都有效,没有不必要的副作用。

InterceptingProvider class 是抽象的,可以用来创建其他类型的不可见 IQueryProvider s。只需很少的工作,您就可以在 AlexJ 的博客中重新创建代码。

好消息是我不会因为暴露而感到厌倦 IQuerable不再从我的数据层。现在业务层可以搞乱 IQueryable所有它想要的,并且没有因为 Entity Framework 异常转义而违反封装的风险。

我唯一喜欢做的就是确保异常被一条消息包裹起来,表明什么操作失败了;例如,“发生错误。无法检索请求的用户。”我喜欢包裹 IQueryable在数据层,但我不知道业务逻辑会对它做什么,直到后来。所以我让业务逻辑负责告诉数据层它的意图是什么。将错误消息字符串传递给数据层,以防万一,有点痛苦,但这仍然比为每个可能的查询定义不同的存储库方法并重写相同的错误处理逻辑 100 次要好。

关于linq - 用于包装异常的 IQueryable,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20623784/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com