Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Entity framework 4 EF 4.2代码优先查询拦截和导航属性_Entity Framework 4 - Fatal编程技术网

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
使用下面的其他类,我可以按如下方式更改my DbContext属性:

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
}