EF学习笔记28:如何实现自己的预先加载(Eager Loading)策略
2010-08-26 16:25
609 查看
背景: 在过去的两年中,很对开发者都抱怨EF中预先加载的执行方式。
下面是你经常会使用的预先加载的方式:
上面代码的意思很明确,要求EF预先加载所有符合要求的Blog的相关的Posts,代码会很好的执行。
但是问题是这里使用了"Posts”这样的字符串的方式,这对于已经习惯于在常规的Linq语句或者Linq to SQL语句中使用强类型的我们来说,用string的这样类型不安全的方式显然不是我们所希望的,我们希望的形式或许是这样的:
下面的方式或许更好:
上面这样的方式可以让你在不同的数据检索之间重用strategies。
设计目标:
根据上面的想法,我打算扩展我的思路来支持这里的strategies。
下面是我想做的针对Blog的Stretegy方式的类的实力:
有了上面的类,你可以像下面这样来使用:
实现:
下面我们来实现上面的这个stretegy类:
1) 创建 IncludeStrategy<T> 类:
从上面的代码中我们注意到,代码使用了一个string数组来存储我们要添加的Includes,同时还有ApplyTo(…)方法允许调用者
可以将所有符合T类型的ObjectQuery<T>, 绑定到Includes中来。
淡然代码主要的功能实在两个Include(..) 方法中,之所以要有两个重载方法,其中第一个是为了加载References引用,另一个是为了加载数组。这样的实现是针对.NET 3.5 SP1的,所以我可以依赖于那些实现了IEntityWithRelationships接口,并具有relationships的类(可以对包含Include的类进行检测)。有趣的是真毒载入数组的Include方法,每一个Expression语句都被设置成isExpression<Func<TEntity, EntityCollection<TNavProp>>> ,而在创建Sub-includes的时候返回的类型却是TNavProp,这样的做法让我们避免在使用的时候使用如下的代码:
Include(b => b.Posts.SelectMany(p => p.Author));
同时也可以避免下面的写法:
Include(b => b.Posts.And().Author);
取而代之的是下面的更简洁和合理的写法:
Include(b => b.Posts).Include(p => p.Author);
上面的实现方式更简单,这也是整个设计中的核心部分。
2) 类IncludeExpressionVisitor 派生自ExpressionVisitor这个实例,你可以在这里找到它,这个实例其实非常简单,或许用在这里有些有过度设计的感觉,但我喜欢在这里采用正确的是寂寞死来实现这个功能。
从上面的代码中你可以看到,vistor仅仅是起了一个约束的作用,它仅仅能够识别LambdaExpressions和MemberExpressions。
当使用MemberExpression的时候,visitor将确认进入的成员是一个属性(Property),并把这个成员直接绑定到其参数中(Parameter)中(比如p.Property可以,但是p.Property.SubProperty不可以)如果出现这种情况,visitor将会记录下NavigationProperty的名字。
3)当我们知道NavigationProperty的名字,IncludeStrategy.Include 方法会创建一个SubInclude<T> 对象,它将用来注册NavigationProperty,并为串联多个sub-includes提供相关的机制。
类SubInclude<T>的代码如下:
4) 现在还遗留的一点东西是一个用于附加其他元素到array数组中的扩展方法,代码如下:
有了上面的代码,你就可以以你自己的方式非常容易的实现与预先加载,你所需要做的就是继承类IncludeStrategy<T>.
但是要注意的是,这个方案只是我的个人方案,并不是微软的官方解决方案,它没有经过严格测试。你可愿意在这里下载到完整代码。
下面是你经常会使用的预先加载的方式:
var results = from b in ctx.Blogs.Include(“°Posts”±) where b.Owner == “°Alex”± select b;
上面代码的意思很明确,要求EF预先加载所有符合要求的Blog的相关的Posts,代码会很好的执行。
但是问题是这里使用了"Posts”这样的字符串的方式,这对于已经习惯于在常规的Linq语句或者Linq to SQL语句中使用强类型的我们来说,用string的这样类型不安全的方式显然不是我们所希望的,我们希望的形式或许是这样的:
var results = from b in ctx.Blogs.Include(b => b.Posts) where b.Owner == “°Alex”± select b; 上面这样的方式是类型安全的,很多人也尝试着这样做。
下面的方式或许更好:
var strategy = new IncludeStrategy<Blog>(); strategy.Include(b => b.Owner); var results = from b in strategy.ApplyTo(ctx.Blogs) where b.Owner == “°Alex”± select b;
上面这样的方式可以让你在不同的数据检索之间重用strategies。
设计目标:
根据上面的想法,我打算扩展我的思路来支持这里的strategies。
下面是我想做的针对Blog的Stretegy方式的类的实力:
var strategy = Strategy.NewStrategy<Blog>(); strategy.Include(b => b.Owner) .Include(p => p.Comments); //sub includes strategy.Include(b => b.Posts); //multiple includes 上面的类将支持子类继承: public class BlogFetchStrategy: IncludeStrategy<Blog> { public BlogFetchStrategy() { this.Include(b => b.Owner); this.Include(b => b.Posts); } }
有了上面的类,你可以像下面这样来使用:
var results = from b in new BlogFetchStrategy().ApplyTo(ctx.Blogs) where b.Owner == “°Alex”± select b;
实现:
下面我们来实现上面的这个stretegy类:
1) 创建 IncludeStrategy<T> 类:
public class IncludeStrategy<TEntity> where TEntity : class, IEntityWithRelationships { private List<string> _includes = new List<string>(); public SubInclude<TNavProp> Include<TNavProp>( Expression<Func<TEntity, TNavProp>> expr ) where TNavProp : class, IEntityWithRelationships { return new SubInclude<TNavProp>( _includes.Add, new IncludeExpressionVisitor(expr).NavigationProperty ); } public SubInclude<TNavProp> Include<TNavProp>( Expression<Func<TEntity, EntityCollection<TNavProp>>> expr ) where TNavProp : class, IEntityWithRelationships { return new SubInclude<TNavProp>( _includes.Add, new IncludeExpressionVisitor(expr).NavigationProperty ); } public ObjectQuery<TEntity> ApplyTo(ObjectQuery<TEntity> query) { var localQuery = query; foreach (var include in _includes) { localQuery = localQuery.Include(include); } return localQuery; } }
从上面的代码中我们注意到,代码使用了一个string数组来存储我们要添加的Includes,同时还有ApplyTo(…)方法允许调用者
可以将所有符合T类型的ObjectQuery<T>, 绑定到Includes中来。
淡然代码主要的功能实在两个Include(..) 方法中,之所以要有两个重载方法,其中第一个是为了加载References引用,另一个是为了加载数组。这样的实现是针对.NET 3.5 SP1的,所以我可以依赖于那些实现了IEntityWithRelationships接口,并具有relationships的类(可以对包含Include的类进行检测)。有趣的是真毒载入数组的Include方法,每一个Expression语句都被设置成isExpression<Func<TEntity, EntityCollection<TNavProp>>> ,而在创建Sub-includes的时候返回的类型却是TNavProp,这样的做法让我们避免在使用的时候使用如下的代码:
Include(b => b.Posts.SelectMany(p => p.Author));
同时也可以避免下面的写法:
Include(b => b.Posts.And().Author);
取而代之的是下面的更简洁和合理的写法:
Include(b => b.Posts).Include(p => p.Author);
上面的实现方式更简单,这也是整个设计中的核心部分。
2) 类IncludeExpressionVisitor 派生自ExpressionVisitor这个实例,你可以在这里找到它,这个实例其实非常简单,或许用在这里有些有过度设计的感觉,但我喜欢在这里采用正确的是寂寞死来实现这个功能。
public class IncludeExpressionVisitor : ExpressionVisitor { private string _navigationProperty = null; public IncludeExpressionVisitor(Expression expr) { base.Visit(expr); } public string NavigationProperty { get { return _navigationProperty; } } protected override Expression VisitMemberAccess( MemberExpression m ) { PropertyInfo pinfo = m.Member as PropertyInfo; if (pinfo == null) throw new Exception( "You can only include Properties"); if (m.Expression.NodeType != ExpressionType.Parameter) throw new Exception( "You can only include Properties of the Expression Parameter"); _navigationProperty = pinfo.Name; return m; } protected override Expression Visit(Expression exp) { if (exp == null) return exp; switch (exp.NodeType) { case ExpressionType.MemberAccess: return this.VisitMemberAccess( (MemberExpression)exp ); case ExpressionType.Lambda: return this.VisitLambda((LambdaExpression)exp); default: throw new InvalidOperationException( "Unsupported Expression"); } } }
从上面的代码中你可以看到,vistor仅仅是起了一个约束的作用,它仅仅能够识别LambdaExpressions和MemberExpressions。
当使用MemberExpression的时候,visitor将确认进入的成员是一个属性(Property),并把这个成员直接绑定到其参数中(Parameter)中(比如p.Property可以,但是p.Property.SubProperty不可以)如果出现这种情况,visitor将会记录下NavigationProperty的名字。
3)当我们知道NavigationProperty的名字,IncludeStrategy.Include 方法会创建一个SubInclude<T> 对象,它将用来注册NavigationProperty,并为串联多个sub-includes提供相关的机制。
类SubInclude<T>的代码如下:
public class SubInclude<TNavProp> where TNavProp : class, IEntityWithRelationships { private Action<string> _callBack; private string[] _paths; internal SubInclude(Action<string> callBack, params string[] path) { _callBack = callBack; _paths = path; _callBack(string.Join(".", _paths)); } public SubInclude<TNextNavProp> Include<TNextNavProp>( Expression<Func<TNavProp, TNextNavProp>> expr ) where TNextNavProp : class, IEntityWithRelationships { string[] allpaths = _paths.Append( new IncludeExpressionVisitor(expr).NavigationProperty ); return new SubInclude<TNextNavProp>(_callBack, allpaths); } public SubInclude<TNextNavProp> Include<TNextNavProp>( Expression<Func<TNavProp, EntityCollection<TNextNavProp>>> expr ) where TNextNavProp : class, IEntityWithRelationships { string[] allpaths = _paths.Append( new IncludeExpressionVisitor(expr).NavigationProperty ); return new SubInclude<TNextNavProp>(_callBack, allpaths); } }
4) 现在还遗留的一点东西是一个用于附加其他元素到array数组中的扩展方法,代码如下:
public static T[] Append<T>(this T[] initial, T additional) { List<T> list = new List<T>(initial); list.Add(additional); return list.ToArray(); }
有了上面的代码,你就可以以你自己的方式非常容易的实现与预先加载,你所需要做的就是继承类IncludeStrategy<T>.
但是要注意的是,这个方案只是我的个人方案,并不是微软的官方解决方案,它没有经过严格测试。你可愿意在这里下载到完整代码。
相关文章推荐
- mfc学习笔记之如何自己动手实现最简单的mfc程序
- android 开发零起步学习笔记(二十):Android开发笔记:如何使用预先制作好的SQLite数据库(整理自网络)
- javaScript学习笔记——如何在加载完某个标签之后执行一个函数
- 黑马程序员之ORACLE数据库学习笔记:如何实现连接池及连接池的优缺点
- 【WPF学习笔记】之如何点击“新建”按钮,在面板中加载一条条的“用户控件”的信息:动画系列之(四)
- 【EF学习笔记09】----------使用 EntityState 枚举标记实体状态,实现增删改查
- iOS学习笔记--如何实现登录按钮显示状态的改变
- [原创]java WEB学习笔记55:Struts2学习之路---详解struts2 中 Action,如何访问web 资源,解耦方式(使用 ActionContext,实现 XxxAware 接口),耦合方式(通过ServletActionContext,通过实现 ServletRequestAware, ServletContextAware 等接口的方式)
- Silverlight学习笔记四:如何通过自定义ComboBox实现SelectedValue
- EF CodeFirst学习笔记003--如何创建表
- iOS学习笔记47——图片异步加载之EGOImageLoading
- 学习笔记TF037:实现强化学习策略网络
- [置顶] iOS学习笔记47——图片异步加载之EGOImageLoading
- Android 实现图片缓存异步加载框架学习笔记
- Android 学习笔记之Volley(七)实现Json数据加载和解析...
- EF学习杂技37:如何实现Conditional Include
- vue.js学习笔记:如何加载本地json文件
- CVP认证学习笔记--李天宇017图片加载的进度实现
- Unity资源打包学习笔记(二)、如何实现高效的unity AssetBundle热更新