From:http://blog.csdn.net/lubingda/article/details/7279678
本节讨论内容主要针对收集了上篇文章大家碰到问题的讨论解决,会持续收集扩充。
- DbContext加载原值,当前值,数据库值,属性操作,对象复制,对象值复制(VO,DTO->POCO),复杂对象取值
- DbContext Local Data与AsNoTracking无跟踪查询 如何提高效率
- DbContext如何关闭延迟加载
- DbContext如可使用延迟加载
- DbContext如何控制并发
- 解除属性映射到数据库中
- 对象失去或没有被跟踪时处理
- 多对多关系之扩展字段字段处理
- 利用模板使模型继承基类
- 如何对象模型中使用枚举值
- 创建或使用代理对象
1. DbContext加载原值,当前值,数据库值,属性操作,对象复制,对象值复制(VO,DTO->POCO),复杂对象取值
View Code
RoRoWoDBEntities context = new RoRoWoDBEntities(); BlogCategory cate = context.Set < BlogCategory > ().Find( 3 ); BlogArticle arti = cate.BlogArticle.ToList().Find(o => o.ArticleID == 2 ); BlogCategory cateOther = context.Set < BlogCategory > ().Find( 4 ); // 取当前值 DbPropertyValues currentValues = context.Entry < BlogCategory > (cate).CurrentValues; // 取原值 DbPropertyValues originalValues = context.Entry < BlogCategory > (cate).OriginalValues; // 取数据库值 DbPropertyValues databaseValues = context.Entry < BlogCategory > (cate).GetDatabaseValues(); // 从数据库读值覆盖原值 context.Entry < BlogCategory > (cate).OriginalValues.SetValues(databaseValues); // 重新加载对象(并发的时候重新从数据库加载对象,注意给当前值做备份) context.Entry < BlogCategory > (cate).Reload(); // 给当前值赋值 context.Entry < BlogCategory > (cate).CurrentValues.SetValues(currentValues); // 复制其它对象值(这一条很强大,支持任何类型,比如ViewObject,DTO与POCO可以直接映射传值) context.Entry < BlogCategory > (cate).CurrentValues.SetValues(cateOther); // 单独给每个对象的属性赋值,并且进行标记 context.Entry < BlogCategory > (cate).Property(o => o.ArticleCount).CurrentValue = 10 ; context.Entry < BlogCategory > (cate).Property(o => o.ArticleCount).IsModified = true ; // 复制对象 BlogCategory newCate = (BlogCategory)context.Entry(cate).GetDatabaseValues().ToObject(); // 复杂对象 给子集合某个属性赋值 context.Entry(arti).ComplexProperty(o => o.BlogCategory).Property(c => c.CateName).CurrentValue = " test " ; // 更改对象状态 context.Entry(arti).State = EntityState.Modified;
2. DbContext Local Data与AsNoTracking无跟踪查询 提高效率
Local Data是通过Load()方法将数据下载到本地,并与Context保持联系,以提高查询效率或者解决批量操作等问题。
View Code
RoRoWoDBEntities context = new RoRoWoDBEntities(); DbSet < BlogArticle > set = context.Set < BlogArticle > (); // 数据加载到本地(你也可以单独下载某一条数据到本地,Local是下载数据的容器) set .Load(); // 从本地查找指定数据 BlogArticle arti = set .Local.ToList().Find(o => o.ArticleID == 7 ); // 由于Local Data与context保持联系,所以本地数据的增删改查一样会生效 // 我们可通过重写SaveChanges避免这种问题发生,让Local Data成为一个查询集合 // 而所有修改均不更新到数据库存中 set .Remove(arti); context.SaveChanges();
重写DbContext SaveChanges方法阻止Local Data数据更新
View Code
public partial class RoRoWoDBEntities : DbContext { public RoRoWoDBEntities() : base ( " name=RoRoWoDBEntities " ) { this .Configuration.LazyLoadingEnabled = false ; } public override int SaveChanges() { // 在更新前清除掉Local中的数据 this .Set < BlogArticle > ().Local.Clear(); return base .SaveChanges(); } ....................... }
AsNoTracking无跟踪查询
View Code
// 实现无跟踪查询提高效率 DbQuery query = set .AsNoTracking(); var result = set .Where(o => o.ArticleID == 7 ).AsNoTracking().ToList();
批量操作关闭自动检测提高效率
View Code
RoRoWoDBEntities context = new RoRoWoDBEntities(); DbSet < BlogArticle > set = context.Set < BlogArticle > (); List < BlogArticle > list = new List < BlogArticle > (); list.Add( new BlogArticle { BlogCategory_CateID = 3 , Content = " 小朋友 " , Title = " 测试001 " }); list.Add( new BlogArticle { BlogCategory_CateID = 3 , Content = " 大朋友 " , Title = " 测试002 " }); try { // 批量操作前 关闭自动检测变化功能 context.Configuration.AutoDetectChangesEnabled = false ; // 批量操作 foreach (var blog in list) { set .Add(blog); } } finally { // 开启自动检测变化 context.Configuration.AutoDetectChangesEnabled = true ; context.SaveChanges(); }
3. DbContext如何关闭开启延迟加载
关闭方式
View Code
// 修改DbContext 配置属性 或者直接设置EDM文件,亦或乾修改tt模板 context.Configuration.LazyLoadingEnabled = false ; // 修改POCO 去掉导航属性的virtual修饰 public partial class BlogArticle { ....................... public virtual BlogCategory BlogCategory { get ; set ; } public virtual ICollection < BlogComment > BlogComment { get ; set ; } public virtual ICollection < BlogDigg > BlogDigg { get ; set ; } }
加载方式
View Code
RoRoWoDBEntities context = new RoRoWoDBEntities(); context.Configuration.LazyLoadingEnabled = false ; BlogArticle arti = context.Set < BlogArticle > ().Find( 7 ); // include方式 BlogCategory cate = context.Set < BlogCategory > ().Include(o => o.BlogArticle).ToList().Find(c => c.CateID == 3 ); // 显示加载 context.Entry(cate).Collection(o => o.BlogArticle).Load(); context.Entry(arti).Reference(o => o.BlogCategory).Load();
4. DbContext如何控制并发
乐观并发的控制
View Code
bool saveFailed; do { saveFailed = false ; var blog = context.Set < BlogArticle > ().Find( 7 ); blog.Title = " 并发修改名称 " ; try { context.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { saveFailed = true ; // 1重新加载已变化的数据 // ex.Entries.Single().Reload(); // 2如果页面会刷新 做好当前值备份 var entry = ex.Entries.Single(); DbPropertyValues values = entry.CurrentValues; entry.Reload(); entry.CurrentValues.SetValues(values); // 3或者给原值重设数据库值 // var entry = ex.Entries.Single(); // entry.OriginalValues.SetValues(entry.GetDatabaseValues()); } } while (saveFailed);
即使使用Timestamp字段,我们一样也是通过乐观并发控制,然后较验timestamp字段是否一致,或者通过存储过程实现这一过程。
5. 解除属性映射到数据库中
EF 提供了一系列属性用于描述模型
- KeyAttribute
- StringLengthAttribute
- MaxLengthAttribute
- ConcurrencyCheckAttribute
- RequiredAttribute
- TimestampAttribute
- ComplexTypeAttribute
- ColumnAttribute
- TableAttribute
- InversePropertyAttribute
- ForeignKeyAttribute
- DatabaseGeneratedAttribute
- NotMappedAttribute 可以通过NotMappedAttribute标记模型某个属性可以使该属性不必映射到数据库。 View Codepublic class Unicorn { public int Id { get ; set ; } [NotMapped] public string Name { get ; set ; } [Timestamp] public byte [] Version { get ; set ; } public int PrincessId { get ; set ; } // FK for Princess reference public virtual Princess Princess { get ; set ; } }
另外我们还可以通过代码的方式,在DbContext的OnModelCreating方法重载中实现,不知道用Code First的人为什么喜欢这种方式处理。DbContext默认是把EDMX文件当成EntityConfiguration加载进来的,和手动处理这些映射关系是一样的。但是手动处理大量的映射关系既不直观,也不容易维护,而EDMX这个模型视图浏览与维护都较之方便啊。
View Code
protected override void OnModelCreating(DbModelBuilder modelBuilder) { // 不映射到数据库中 modelBuilder.Entity < BlogArticle > ().Ignore(p => p.Title); }
EDMX怎么解除映射还没有找到如何实现,删除表映射EDM验证无法通过,而对象属性又没提供NotMapped 选择。
我觉得如果一个属性不需要映射到数据库时,那这个POCO对象设计肯定是不合理的,可能他是一个合理VO对象,或者是数据库查询视图对象。
6. 对象失去或没有被跟踪时处理
context.Set<BlogArticle>().Attach() 加回上下文中,继续跟踪
7. 多对多关系之扩展字段字段处理
EF 处理多对多关系,是通过中间表主外键关联加载对应的主体表对象,这个中间表是个不存在的业务对象。而中间表如果除了主外键之外还有扩展的字段,就会导致中间表变成一个具体存在的业务对象,让它成为对应主体表的关联导航属性。因此这种中间表设计是不合理,但EF可以应对这种情况。看下图
Student<-Score(多对多中间表)->Subject 三个对象 由于Score不是一个合理的中间表,导致EF将其映射为一个具体的实体对象成为Student与Subject的导航属性。
而User<-UserProperty(多对多中间表)->Property UserProperty仅是数据关系的体现,并不是一个具体的实体对象,User与Property是直接导航。
8. 利用模板使模型继承基类
由于我们的纯POCO模型没有基类限制领域,所以在我们的泛型传递POCO的对象给DAL时,就无法限制这个T 是POCO对象呢,还是其它的对象。因此为了将POCO对象与其它业务对象区分开来,可以通过使POCO对象继承一个基类来解决这个问题,当然肯定是模板解决这个问题了。
我们先立一个POCOEntity基类
View Code
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace EF.Model { public class POCOEnity { // 随便扩展操作属性 // 假删除 public bool IsFakeDelete { set ; get ; } // 脏数据 public bool IsDirty { set ; get ; } } }
再打DemoDB.tt 模版,找到这段代码(或者直接搜索partial),修改为:
View Code
< # = Accessibility.ForType(entity)# > < # = code.SpaceAfter(code.AbstractOption(entity))# > partial class < # = code.Escape(entity)# >< # = code.StringBefore( " : " , code.Escape(entity.BaseType))# > :POCOEnity
即在模版生成类结构代码追加其继承POCOEnity基类,修改完成后,点击保存,所有模型类会重新生成,我们看一下生成后的代码
View Code
// ------------------------------------------------------------------------------ // <auto-generated> // 此代码是根据模板生成的。 // // 手动更改此文件可能会导致应用程序中发生异常行为。 // 如果重新生成代码,则将覆盖对此文件的手动更改。 // </auto-generated> // ------------------------------------------------------------------------------ namespace EF.Model { using System; using System.Collections.Generic; public partial class BlogArticle:POCOEnity { public BlogArticle() { this .BlogComment = new HashSet < BlogComment > (); this .BlogDigg = new HashSet < BlogDigg > (); } public int ArticleID { get ; set ; } public string Title { get ; set ; } public string Content { get ; set ; } public string Description { get ; set ; } public string ImageUrl { get ; set ; } public string Tag { get ; set ; } public int Hits { get ; set ; } public bool IsTop { get ; set ; } public int State { get ; set ; } public Nullable < int > UserID { get ; set ; } public string UserName { get ; set ; } public string UserIP { get ; set ; } public Nullable < System.DateTime > CreateTime { get ; set ; } public Nullable < System.DateTime > PublishTime { get ; set ; } public Nullable < System.DateTime > UpdateTime { get ; set ; } public string Note { get ; set ; } public int BlogCategory_CateID { get ; set ; } public virtual BlogCategory BlogCategory { get ; set ; } public virtual ICollection < BlogComment > BlogComment { get ; set ; } public virtual ICollection < BlogDigg > BlogDigg { get ; set ; } } }
这个时候我们就可以控制DAL传入的泛型模型参数T的域范围了
public interface IRepository < T > where T : POCOEnity, new () public abstract class RepositoryBase < T > :IRepository < T > where T :POCOEnity, new ()
9. 如何对象模型中使用枚举值
插件:
昨晚整了一下,整个VS2010打完补丁后,所有模板都出现2个,程序集还不一致。-_-|||悲剧,看一下微软ADO.NET 团队博客的图
想自己实现的朋友,可以参照 。我觉得枚举就是一个转换的过程,不一定非要通过枚举实现,毕竟整个项目用的枚举很多,而这些枚举肯定是存放在一张表里,而不是一个枚举一张表一个对象,若是这样处理起来肯定是给自己增加麻烦。
10. 创建或使用代理对象
首先开启代理会影响到实体对象的序列化操作(可能无法序列化),但是禁用代理又无法延迟加载导航数据。延迟加载无非是访问这个属性时会自动加载导航属性数据,如果项目需要对象序列化,我们可以禁用代理并闭延迟加载以提高效率,当需要使用导航属性可以使用DbContext.Entity 加载导航数据。
禁用代理对象 this.Configuration.ProxyCreationEnabled = false;
当New一个模型时 BlogCategory cate = new BlogCategory(); 则是创建实体对象
而通过DbSet<POCO>.Create() ,则是显示创建代理。
按照MSDN解释,关闭代理就无法跟踪对象的变化了,但实际上我的测试结果是两种情况都可以跟踪到对象的状态变化。不知道MSDN确切指的是什么?
这一节会持续收集问题更新,大家在使用中碰到的问题,可以拿出来一起讨论讨论。