Entity framework 4 EF 4.2代码优先查询拦截和导航属性
注意:我的项目中的实际实体是不同的。您将要阅读的场景是一个简化的示例。我的实际示例影响的实体比这里列出的要多得多 在我的项目中,我有一个成员和组类,如下所示:Entity framework 4 EF 4.2代码优先查询拦截和导航属性,entity-framework-4,Entity Framework 4,注意:我的项目中的实际实体是不同的。您将要阅读的场景是一个简化的示例。我的实际示例影响的实体比这里列出的要多得多 在我的项目中,我有一个成员和组类,如下所示: public class Member { public string Name { get; set; } public IList<Group> Groups { get; set; } } public class Group { public string Name { get; set; }
public class Member
{
public string Name { get; set; }
public IList<Group> Groups { get; set; }
}
public class Group
{
public string Name { get; set; }
public IList<Member> Members { get; set; }
}
public class Db : DbContext
{
public IQueryable<Group> Groups
{
get
{
return from g in ((IObjectContextAdapter)this).CreateQuery<Group>("SELECT VALUE Groups FROM Groups")
where g.Name.StartsWith("X")
select g;
}
}
public IQueryable<Member> Members
{
get
{
return from m in ((IObjectContextAdapter)this).CreateQuery<Member>("SELECT VALUE Members FROM Members")
where m.Name.StartsWith("X")
select m;
}
}
}
这里的问题与…有关
var members = db.Members.Include(m => m.Groups).ToList();
var groups = db.Groups.Include(g => g.Members).ToList();
虽然“成员”列表中只有名称以“X”开头的“成员”,但Groups属性包含名称不符合要求的组对象。同样的模式也适用于“组”列表,成员对象不符合
EF4.2中是否有我遗漏的功能
如何影响从导航属性生成的查询?请仔细查看。注意:我意识到这可能需要一些改进。它在我的场景中起作用。不保证编译的代码 我最终构建的是一种覆盖查询执行的方法,并结合中描述的方法,用所需的相关对象填充DbContext的缓存 设计目标:
- 提供拦截查询执行的方法
- 需要防止执行某些“Include”语句,同时知道查询编写器要“包括”哪些导航属性
- 需要填充缓存并将查询重定向到缓存,以防止对远程存储进行不必要的调用
- 创建一个
ReLinqContext
- 设置包括过滤选项(可选)
- 定义截取函数,该函数接受
并返回IQueryable
,并将该函数分配给IQueryable
ReLinqContext
- 定义一个基本查询,并调用
扩展方法,从上面传入AsReLinqQuery()
ReLinqContext
public class Member
{
public string Name { get; set; }
public IList<Group> Groups { get; set; }
}
public class Group
{
public string Name { get; set; }
public IList<Member> Members { get; set; }
}
public class Db : DbContext
{
public IQueryable<Group> Groups
{
get
{
return from g in ((IObjectContextAdapter)this).CreateQuery<Group>("SELECT VALUE Groups FROM Groups")
where g.Name.StartsWith("X")
select g;
}
}
public IQueryable<Member> Members
{
get
{
return from m in ((IObjectContextAdapter)this).CreateQuery<Member>("SELECT VALUE Members FROM Members")
where m.Name.StartsWith("X")
select m;
}
}
}
Get for DbContext.Groups属性的示例主体
//启用对ReLinq查询的跟踪
var ctx=新ReLinqContext();
//将ctx配置为日志,但避免执行Include(string)方法
ctx.DisableIncludes();
ctx.Intercept=q=>
{
//从数据库集中提取ObjectQuery
//必须这样做才能使q.ChangeSource工作
var groups=来自Set.AsObjectQuery()中的一个;
//重写查询以指向新的数据源。。。
var Rewritenq=q.ChangeSource(组);
//将结果加载到上下文中。。。
重写q.Load();
//从缓存中获取组ID
var groupIds=(来自Set()中的g.Local
选择g.Id).ToList();
//将各自的成员对象加载到上下文中。。。
如果(ctx.IncludePath.Contains(“成员”))
{
var成员=来自集合()中的m
从g到m.群
其中groupid.Contains(g.Id)和&m.Name.StartsWith(“X”)
选择m;
成员。加载();
}
//在此处添加额外的if(ctx.IncludePaths.Contains(“…”)检查
//返回将对DbContext缓存执行的查询
返回q.ChangeSource(Set().Local.AsQueryable());
};
//拦截期间对ChangeSource的调用
//将允许返回实际数据。
返回新组[0]。AsReLinqQuery(ctx);
实现relinqquerys的附加代码
公共静态类ReLinqExtensions
{
公共静态IQueryable变更源(此IQueryable oldSource、IQueryable newSource)
{
return(IQueryable)QueryableRebinder.Rebind(oldSource,newSource);
}
公共静态IReLinqQuery AsReLinqQuery(此IEnumerable可枚举,IReLinqContext上下文)
{
返回AsReLinqQuery(enumerable.AsQueryable(),context);
}
公共静态IReLinqQuery AsReLinqQuery(此IQueryable查询,IReLinqContext上下文)
{
返回新的ReLinqQuery(查询,(IReLinqContext)上下文);
}
公共静态IReLinqContext DisableIncludes(此IReLinqContext上下文)
{
context.allowicludePath=path=>false;
返回上下文;
}
}
公共静态类DbSetExtensions
{
公共静态ObjectQuery AsObjectQuery(此DbSet源),其中T:class
{
return(ObjectQuery)DbSetUnwrapper.UnWrap(source);
}
}
公共接口IReLinqContext
{
IList IncludePaths{get;}
委托截获{get;}
Func AllowIncludePath{get;}
}
公共接口IReLinqContext
{
IEnumerable IncludePaths{get;}
Func拦截{get;set;}
Func AllowIncludePath{get;set;}
}
公共类ReLinqContext:IReLinqContext,IReLinqContext
{
私有只读IList(包括路径);
公共ReLinqContext()
{
_includePaths=新列表();
IncludePaths=新的只读集合(\u IncludePaths);
截距=q=>q;
AllowIncludePath=path=>true;
}
公共IEnumerable IncludePath{get;私有集;}
公共函数截取{get;set;}
公共函数allowicludepath{get;set;}
IList IReLinqContext.includePath{get{return}
委托IReLinqContext.Intercept
{
得到
{
回程截获;
}
}
}
公共接口IRelinkQuery:IOrderedQueryable
{
IReLinqContext上下文{get;}
IReLinqQuery包含(字符串路径);
}
公共类ReLinqQuery:IReLinqQuery
{
公共IReLinqContext上下文{get;private set;}
私有表达式=null;
private ReLinqQueryProvider提供程序=null;
公共ReLinqQuery(IQueryable源,IReLinqContext上下文)
{
上下文=(IReLinqContext)上下文;
表达式=表达式常数(this);
提供者=新的ReLinqQueryProvider(源、上下文);
}
公共ReLinqQuery(IQueryable源、IReLinqContext上下文、表达式e)
{
如果(e==null)抛出新的ArgumentNullException(“e”);
表达式=e;
提供者=新的ReLinqQueryProvider(源、上下文);
// Enables tracking of ReLinq queries
var ctx = new ReLinqContext<Group>();
// Configures ctx to log, but avoid execution of the Include(string) method
ctx.DisableIncludes();
ctx.Intercept = q =>
{
// Extract the ObjectQuery<T> out of the DbSet<T>
// This must be done for q.ChangeSource to work
var groups = from a in Set<Group>.AsObjectQuery();
// Rewrite the query to point to a new data source...
var rewrittenQ = q.ChangeSource(groups);
// load the results into the context...
rewrittenQ.Load();
// Get group ids from the cache
var groupIds = (from g in Set<Group>().Local
select g.Id).ToList();
// Load respective Member objects into the context...
if (ctx.IncludePaths.Contains("Members"))
{
var members = from m in Set<Member>()
from g in m.Groups
where groupIds.Contains(g.Id) && m.Name.StartsWith("X")
select m;
members.Load();
}
// Add additional if (ctx.IncludePaths.Contains("...")) checks here
// Return a query that will execute against the DbContext cache
return q.ChangeSource(Set<Group>().Local.AsQueryable());
};
// The call to ChangeSource during interception
// will allow actual data to be returned.
return new Group[0].AsReLinqQuery(ctx);
public static class ReLinqExtensions
{
public static IQueryable<T> ChangeSource<T>(this IQueryable<T> oldSource, IQueryable<T> newSource)
{
return (IQueryable<T>) QueryableRebinder.Rebind(oldSource, newSource);
}
public static IReLinqQuery<T> AsReLinqQuery<T>(this IEnumerable<T> enumerable, IReLinqContext<T> context)
{
return AsReLinqQuery(enumerable.AsQueryable(), context);
}
public static IReLinqQuery<T> AsReLinqQuery<T>(this IQueryable<T> query, IReLinqContext<T> context)
{
return new ReLinqQuery<T>(query, (IReLinqContext)context);
}
public static IReLinqContext<T> DisableIncludes<T>(this IReLinqContext<T> context)
{
context.AllowIncludePath = path => false;
return context;
}
}
public static class DbSetExtensions
{
public static ObjectQuery<T> AsObjectQuery<T>(this DbSet<T> source) where T : class
{
return (ObjectQuery<T>)DbSetUnwrapper.UnWrap(source);
}
}
public interface IReLinqContext
{
IList<string> IncludePaths { get; }
Delegate Intercept { get; }
Func<string, bool> AllowIncludePath { get; }
}
public interface IReLinqContext<T>
{
IEnumerable<string> IncludePaths { get; }
Func<IQueryable<T>, IQueryable<T>> Intercept { get; set; }
Func<string, bool> AllowIncludePath { get; set; }
}
public class ReLinqContext<T> : IReLinqContext<T>, IReLinqContext
{
private readonly IList<string> _includePaths;
public ReLinqContext()
{
_includePaths = new List<string>();
IncludePaths = new ReadOnlyCollection<string>(_includePaths);
Intercept = q => q;
AllowIncludePath = path => true;
}
public IEnumerable<string> IncludePaths { get; private set; }
public Func<IQueryable<T>, IQueryable<T>> Intercept { get; set; }
public Func<string, bool> AllowIncludePath { get; set; }
IList<string> IReLinqContext.IncludePaths { get { return _includePaths; }}
Delegate IReLinqContext.Intercept
{
get
{
return Intercept;
}
}
}
public interface IReLinqQuery<T> : IOrderedQueryable<T>
{
IReLinqContext<T> Context { get; }
IReLinqQuery<T> Include(String path);
}
public class ReLinqQuery<T> : IReLinqQuery<T>
{
public IReLinqContext<T> Context { get; private set; }
private Expression expression = null;
private ReLinqQueryProvider provider = null;
public ReLinqQuery(IQueryable source, IReLinqContext context)
{
Context = (IReLinqContext<T>)context;
expression = Expression.Constant(this);
provider = new ReLinqQueryProvider(source, context);
}
public ReLinqQuery(IQueryable source, IReLinqContext context, Expression e)
{
if (e == null) throw new ArgumentNullException("e");
expression = e;
provider = new ReLinqQueryProvider(source, context);
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)provider.Execute(expression)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)provider.Execute(expression)).GetEnumerator();
}
public IReLinqQuery<T> Include(String path)
{
((IReLinqContext)Context).IncludePaths.Add(path);
if (!Context.AllowIncludePath(path))
{
return this;
}
var possibleObjectQuery = provider.Source as DbQuery<T>;
if (possibleObjectQuery != null)
{
return new ReLinqQuery<T>(possibleObjectQuery.Include(path), (IReLinqContext)Context);
}
return this;
}
public Type ElementType
{
get { return typeof(T); }
}
public Expression Expression
{
get { return expression; }
}
public IQueryProvider Provider
{
get { return provider; }
}
}
public class ReLinqQueryProvider : IQueryProvider
{
internal IQueryable Source { get; private set; }
internal IReLinqContext Context { get; private set; }
public ReLinqQueryProvider(IQueryable source, IReLinqContext context)
{
if (source == null) throw new ArgumentNullException("source");
Source = source;
Context = context;
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
if (expression == null) throw new ArgumentNullException("expression");
return new ReLinqQuery<TElement>(Source, Context, expression);
}
public IQueryable CreateQuery(Expression expression)
{
if (expression == null) throw new ArgumentNullException("expression");
Type elementType = expression.Type.GetGenericArguments().Single();
IQueryable result = (IQueryable)Activator.CreateInstance(typeof(ReLinqQuery<>).MakeGenericType(elementType),
new object[] { Source, Context, expression });
return result;
}
public TResult Execute<TResult>(Expression expression)
{
if (expression == null) throw new ArgumentNullException("expression");
object result = (this as IQueryProvider).Execute(expression);
return (TResult)result;
}
public object Execute(Expression expression)
{
if (expression == null) throw new ArgumentNullException("expression");
var translated = ReLinqQueryUnwrapper.UnWrap(expression, Source);
var translatedQuery = Source.Provider.CreateQuery(translated);
//var query = CreateQuery(expression);
var interceptedQuery = Context.Intercept.DynamicInvoke(translatedQuery);
return interceptedQuery;
}
}
public class ReLinqQueryUnwrapper : ExpressionVisitor
{
private readonly IQueryable _source;
public static Expression UnWrap(Expression expression, IQueryable source)
{
var queryTranslator = new ReLinqQueryUnwrapper(source);
return queryTranslator.Visit(expression);
}
public ReLinqQueryUnwrapper(IQueryable source)
{
_source = source;
}
#region Visitors
protected override Expression VisitConstant(ConstantExpression c)
{
if (c.Type == typeof(ReLinqQuery<>).MakeGenericType(_source.ElementType))
{
return _source.Expression;
}
return base.VisitConstant(c);
}
#endregion
}
public class DbSetUnwrapper : ExpressionVisitor
{
public static IQueryable UnWrap(IQueryable source)
{
var dbSetUnwrapper = new DbSetUnwrapper(source);
dbSetUnwrapper.Visit(source.Expression);
return dbSetUnwrapper._target;
}
private readonly IQueryable _source;
private IQueryable _target;
public DbSetUnwrapper(IQueryable source)
{
_source = source;
}
public override Expression Visit(Expression node)
{
if(node.NodeType == ExpressionType.Constant)
{
var c = (ConstantExpression) node;
if (c.Type == typeof(ObjectQuery<>).MakeGenericType(_source.ElementType))
{
_target = (IQueryable)c.Value;
}
}
return base.Visit(node);
}
}
public class QueryableRebinder : ExpressionVisitor
{
private IQueryable _oldSource;
private IQueryable _newSource;
public static IQueryable Rebind(IQueryable oldSource, IQueryable newSource)
{
var queryTranslator = new QueryableRebinder(oldSource, newSource);
return newSource.Provider.CreateQuery(queryTranslator.Visit(oldSource.Expression));
}
public QueryableRebinder(IQueryable oldSource, IQueryable newSource)
{
_oldSource = oldSource;
_newSource = newSource;
}
#region Visitors
protected override Expression VisitConstant(ConstantExpression c)
{
if (typeof(IQueryable<>).MakeGenericType(_oldSource.ElementType).IsAssignableFrom(c.Type))
{
return Expression.Constant(_newSource);
}
return base.VisitConstant(c);
}
#endregion
}