我正在构建LINQ扩展以通过EF简化数据库访问,并且该流程的一部分是将数据实体映射到业务实体。
我使用Dictionary<string, int>
来决定要包含的导航属性以及深度。
示例:
public static class LinqExtensions
{
private static readonly Dictionary<string, int> Dictionary = new Dictionary<string, int>();
/// <summary>
/// Adds the navigational property identified by value to be included in the query and entity mapping, recursing a maximum of depth times.
/// </summary>
/// <param name="value">Navigational Property to add</param>
/// <param name="depth">Desired recursion depth</param>
public static TSource With<TSource>(this TSource source, string value, int depth = 0)
{
Dictionary.Add(value, depth);
return source;
}
/// <summary>
/// Clears the navigational property dictionary
/// </summary>
public static void Reset()
{
Dictionary.Clear();
}
/// <summary>
/// Builds and executes a query, dynamically including desired navigational properties in a asynchronous fashion.
/// The result is then mapped to the provided TResult business entity and returned as a list.
/// </summary>
/// <returns>Null or a list of mapped domain Entities</returns>
public static async Task<IEnumerable<TResult>> BuildQueryAsync<TSource, TResult>(this IQueryable<TSource> dbEntity) where TResult : class, new()
{
var query = dbEntity;
var localDictionary = new Dictionary<string, int>(Dictionary);
Reset();
foreach (var i in localDictionary)
{
query = query.Include(i.Key);
}
List<TSource> result = await (from entity in query select entity).ToListAsync();
return Equals(result, default(TSource)) ? null : result.Select(u => u.BuildEntity(new TResult(), localDictionary));
}
/// <summary>
/// Maps values from sourceEntity to targetEntity, recursing into properties defined in localDictionary.
/// </summary>
public static TTarget BuildEntity<TSource, TTarget>(this TSource sourceEntity, TTarget targetEntity, Dictionary<string, int> localDictionary)
{
return (TTarget)targetEntity.InjectFrom(new SinglePropertyDepthInjection(localDictionary), sourceEntity);
}
}
这让我可以访问我的存储库和服务中的内容,如下所示:
public override async Task<IEnumerable<User>> GetAllAsync()
{
return await _context.Users.With("Messages", 1).With("Notifications", 2).BuildQueryAsync<Data.Entities.User, User>();
}
现在我很清楚这是不可行的,因为所有请求都共享静态属性。
我知道我可以轻松地添加一个字典作为方法参数,并解决它:
public override async Task<IEnumerable<User>> GetAllAsync()
{
var dict = new Dictionary<string, int>();
dict.Add("Messages", 1);
dict.Add("Notifications", 2);
return await _context.Users.BuildQueryAsync<Data.Entities.User, User>(dict);
}
但我想知道是否有更优雅的解决方案,理想情况下将其作为LINQ查询的一部分。
我知道有HttpContext.Current
,但由于涉及的方法是异步的,我不确定回到上下文线程的想法有多好。
有什么想法吗?
答案 0 :(得分:1)
我认为CallContext可能就是你要找的东西。
与一次性图案相结合,这样的东西可以非常简单。
public class IncludeScope : IDisposable
{
private const string CallContextKey = "IncludeScopKey";
private object oldValue;
public IncludeScope(IDictionary<string,int> values)
{
this.oldValue = CallContext.GetData(CallContextKey);
this.Includes = new Dictionary<string,int>(values);
CallContext.SetData(CallContextKey, this);
}
public Dictionary<string,int> Includes { get; private set; }
public static IncludeScope Current {
get { return CallContext.GetData(CallContextKey) as IncludeScope; }
}
private bool _disposed;
protected virtual bool IsDisposed
{
get
{
return _disposed;
}
}
~IncludeScope()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed) {
if (disposing) {
CallContext.SetData(CallContextKey, oldValue);
}
_disposed = true;
}
}
}
范围可以这样声明。
using(var scope = new IncludeScope(new Dictionary<string,int>{{"Message",1}, {"Notifications",2}})){
var query = await GetQueryAsync<User>();
…
}
在使用范围内的任何方法调用中,可以像这样访问范围。
private static Task<IQueryable<T>> GetQueryAsync<T>() {
var baseQuery = context.Set<T>();
foreach (var include in IncludeScope.Current.Includes) {
}
}