热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

(翻译)EntityFramework技巧系列之四-Tip13–15

提示13.附加一个实体的简单方式问题:在早先的一些提示中,我们讨论了使用Attach来加载一个处于未改变(unchanged)状态的东西到ObjectContext从而避免进行查询的开销。

提示13. 附加一个实体的简单方式

问题:

在早先的一些提示中,我们讨论了使用Attach来加载一个处于未改变(unchanged)状态的东西到ObjectContext从而避免进行查询的开销。

如果性能是你的目标,Attach就是要选择的武器。

不幸的是我们的API不能适应99%的情况,即每个类型仅有一个实体集(entity set)的情况。Entity Framework支持单类型多实体集(Multiple Entity Sets per Type)或称MEST,且API反映了这一点,要求你提供你要附加的实体集(EntitySet)的名称。

即,像这样:

1 ctx.Attach("Orders", order); 

如果你像我一样,可能你也会发反感在代码里硬编码入字符串。它们容易出错且这类东西会污染你的代码,这实质上是一个"小问题"。

 

.NET 4.0中的解决方案

在.NET 4.0中通过每一个EntitySet返回ObjectSet而不是ObjectQuery这个强类型的属性修复了这个问题。ObjectSet有Add, Delete及Attach方法直接处理这个问题,所以你可以写如下这样的代码:

1 ctx.Order.Attach(order);

没有一个字符串出现!

这种解决方案是理想的,你附加需要的实体集,且无论你是否有MEST,它都工作。

 

.NET 3.5中的解决方案

.NET 3.5中应该怎么办呢?

我的观点是,我们应该提供一个泛型版本的Attach,即如下这样:

1 void AttachToDefaultSet(T Entity); 

这个方法会检查T中存在多少个EntitySet,如果只有一个,其将附加这个实体到那个实体集。然而如果多于一个其将抛出异常。

虽然这种方法没有扩展方法的力量,但它也很容易写。

以下是你需要做的:

  1. 得到类型的EntityType
  2. 得到这个EntityType可能属于的EntitySet类型的列表。EntityType可能为派生类型(像Car),并且实际上属于一个父类型集合(像Vehicles)。
  3. 遍历EntityContainer,对每一个EntityContainer的EntitySet查找一个匹配。
  4. 如果找到一个则进行附加,否则抛出异常。

下面让我们来完成:

但首先要注意,这只是示例级质量的代码,我是一个项目经理而非程序员,所以使用这些代码的风险自担:)

首先我们添加一个扩展方法到MetadataWorkspace来获取一个CLR类型(O-Space)对应的概念模型(C-Space)EntityType

 1 public static EntityType GetCSpaceEntityType( 
2 this MetadataWorkspace workspace
3 )
4 {
5 if (workspace == null)
6 throw new ArgumentNullException("workspace");
7 // Make sure the assembly for "T" is loaded
8 workspace.LoadFromAssembly(typeof(T).Assembly);
9 // Try to get the ospace type and if that is found
10 // look for the cspace type too.
11 EntityType ospaceEntityType = null;
12 StructuralType cspaceEntityType = null;
13 if (workspace.TryGetItem(
14 typeof(T).FullName,
15 DataSpace.OSpace,
16 out ospaceEntityType))
17 {
18 if (workspace.TryGetEdmSpaceType(
19 ospaceEntityType,
20 out cspaceEntityType))
21 {
22 return cspaceEntityType as EntityType;
23 }
24 }
25 return null;
26 }

由于你可能在的元数据加载前调用这段代码,代码第一行保证了这个程序集被加载,如果程序集已被加载其不会执行任何操作。

下一步我们添加一个方法得到一个我们需要匹配的所有类型的枚举,即,包括当前类型在内的父类型的层级:

 1 public static IEnumerable GetHierarchy( 
2 this EntityType entityType)
3 {
4 if (entityType == null)
5 throw new ArgumentNullException("entityType");
6 while (entityType != null)
7 {
8 yield return entityType;
9 entityType = entityType.BaseType as EntityType;
10 }
11 }

最后,我们可以开始完成AttachToDefaultSet方法:

 1 public static void AttachToDefaultSet( 
2 this ObjectContext ctx,
3 T entity)
4 {
5 if (ctx== null) throw new ArgumentNullException("ctx");
6 if (entity == null) throw new ArgumentNullException("entity");
7
8
9 MetadataWorkspace wkspace = ctx.MetadataWorkspace;
10 EntitySet set = wkspace
11 .GetEntitySets(wkspace.GetCSpaceEntityType())
12 .Single();
13
14 ctx.AttachTo(set.Name, entity);
15 }

这里使用了标准的.Single()方法,如果不是恰好存在一个对应EntityType的可能的实体集其将抛出一个异常。

使用这个实现,我们可以将前文的代码使用下面这种方式重写:

1 Product p = newProduct { ID = 1, Name = "Chocolate Fish" } ctx.AttachToDefaultSet(p);

当然,除非你使用MEST...但你可能不使用!

附加说明

虽然这段代码可以很好的工作,但其确实没有进行过任何优化。

或许缓存对应一个CLR类型的可能的集合的名字是有意义的,这样当你进行Attach的时候就无需再进行相同的检查,这就当留给你的练习了!

提示索引

是的,这里有一个本系列剩余提示的索引。

 

提示14. 怎样缓存Entity Framework引用数据

场景:

    为了使应用程序可以工作,缓存常用的引用数据是非常有意义的。

    引用数据的好例子包括像States, Countries, Departments等事物。

通常你想要这些数据随时可用,以方便的进行填充下拉列表等操作。

    什么地方将引用数据缓存在手边的一个好例子是让新客户注册的页面,表单的一部分收集用户的地址,包括他们的州。在这个例子中你需要引用数据做两件事:

1. 构建表单中选择州的下拉列表。

2. 将州指定到最终的用户记录。

你怎样使用Entity Framework来支持这类场景呢?

解决方案:

当设计解决方案时我们需要记住两个关键点。

1. 一个实体同一时间只能被附加到一个ObjectContext,至少.NET 3.5 SP1中是这样。

2. 你可能由许多线程同时使用缓存的引用数据(读ObjectContexts)。

本质上这两点相互矛盾。

解决方案是当我们由缓存读取时拷贝实体,这样附加拷贝将不会影响任何其它线程。

如果这是一个webform解决方案,我们可能要写这样的代码:

 1 var customer = new Customer{ 
2 Firstname = txtFirstname.Text,
3 Surname = txtSurname.Text,
4 Email = txtEmail.Text,
5 Street = txtStreet.Text,
6 City = txtCity.Text,
7 State = statesCache.GetSingle(
8 s => s.ID = Int32.Parse(ddState.SelectedValue)
9 ),
10 Zip = txtZip.Text
11 }
12 ctx.AddToCustomers(customer);
13 ctx.SaveChanges();

但是这有一个大问题。当你添加customer到ObjectContext时,拷贝的State也是added状态。如果我们这样做,Entity Framework会认为其需要将State插入到数据库。而这不是我们想要的。

所以我们需要通过使用 AttachTo(...) 告诉Entity Framework State这个拷贝的已经存在于数据库中:

1 var state = statesCache.GetSingle( 
2 s => s.ID = Int32.Parse(ddState.SelectedValue)
3 );
4 // See Tip 13 to avoid specifying the EntitySet
5 // as a string
6 ctx.AttachTo("States", s);

然后我们可以继续构建customer:

 1 var customer = new Customer{ 
2 Firstname = txtFirstname.Text,
3 Surname = txtSurname.Text,
4 Email = txtEmail.Text,
5 Street = txtStreet.Text,
6 City = txtCity.Text,
7 State = state,
8 Zip = txtZip.Text
9 }
10 ctx.SaveChanges();

如果你足够警惕,你可能已经发现我没有再次调用 AddToCustomers(...) 

为什么呢?嗯,当你构建一个到已存在于context (State = state)中的关系时,这个customer会自动被添加。

现在,在 SaveChanges() 被调用时,只有Customer被存储到数据库。State根本不会被持久化,因为Entity Framework认为其不曾改变。

有趣的是,我们可以利用State不被支持化的事实作为我们的条件。

因为,State的主键属性是Entity Framework构建关系时唯一需要知道的,即使其它属性都错了也没有关系,主键属性实际上是我们拷贝时唯一需要的。

这样,我们的拷贝代码可以非常简单:

1 public State Clone(State state) 
2 {
3 return new State {ID = state.ID};
4 }

或者使用如下lambda表达式:

1 var clOner= (State s) => new State {ID = s.ID};

只要我们不想要修改拷贝,这些就是所有我们实际需要的。

现在我们知道了需要什么,编写一个非常简单的提供缓存与"只读拷贝"服务的泛型类就很容易了。

 1 public class CloningCache where T : class 
2 {
3 private List _innerData;
4 private Func _cloner;
5 public CloningCache(IEnumerable source, Func cloner)
6 {
7 _innerData = source.ToList();
8 _clOner= cloner;
9 }
10 public T GetSingle(Funcbool> predicate)
11 {
12 lock (_innerData)
13 {
14 return _innerData
15 .Where(predicate)
16 .Select(s => _cloner(s))
17 .Single();
18 }
19 }
20 }

注意, GetSingle(...) 方法拷贝它找到的结果。

另外使用这个拷贝缓存非常简单:

1 var statesCache = new CloningCache( 
2 ctx.States,
3 (State s) => new State {ID = s.ID}
4 );

构造函数的第一个参数是要缓存的数据(即数据库中所有的States),第二个参数是怎样实现拷贝,我们需要跨多个ObjectContext安全的使用这个缓存。

一旦你初始化了这个缓存(大概是在Global.asax中),无论什么情况下你需要直接访问引用数据,你都可以在一个静态变量中情况此缓存。

如果哪里讲的不清楚或你有什么问题,请告诉我。

 

提示15. 怎样避免加载非必须的属性

更新:对之前需要Original值做了一系列重要的更正。

问题:

想象如果你查询博客随笔:

1 var myPosts = from post in ctx.Posts  
2 orderby post.Created descending
3 select post;

仅仅这样你就可以输出博客标题等等。

1 foreach(var post in myPosts) 
2 {
3 Console.WriteLine("{0} on {1}", post.Title, post.Created);
4 }

这样你做了一大些无用的工作来加载你实际上不需要的属性。

只读解决方案:

对于只读场景,解决方案很容易。

你只需进行投影操作:

1 var myPosts = from post in ctx.Posts  
2 orderby post.Created descending
3 select new {post.Title, post.Created};

这样你就避免了加载你实际上不需要的属性。

对于有许多属性或存在映射到数据库中一个blob列的属性的实体,例如像Body之类映射到一个nvarchar(max)列的属性,这尤其重要。

读写解决方案:

但如果你需要修改实体该怎么办呢?

此处投影不是一个好方案,因为除非你获取一个完整的对象,否则你将不会得到任何对象服务,这意味着将无法更新。

嗯…

    一如往常,得到一个解决方案的关键的就是理解Entity Framework的工作方式。

当更新一个实体,Entity Framework将更新以如下格式发送到数据库(伪代码):

 1 UPDATE [Table] 
2 SET
3 ModifiedProperty1 = NewValue1,
4 ModifiedProperty2 = NewValue2,
5 ...
6 ModifiedPropertyN = NewValueN
7 WHERE
8 KeyProperty = KeyValue AND
9 ModifiedProperty1 = OriginalValue1 AND
10 ModifiedProperty2 = OriginalValue2 AND
11 ...
12 ModifiedPropertyN = OriginalValueN

注意没有修改过的属性不会出现在更新命令的任何地方。

重大发现:这意味着你只需要知道主键属性的原始值即可。*

带着这些发现,我们可以进行下面这样的尝试:

  1. 仅投射出我们需要读写的列
  2. 由投影伪造一个实体,忽略我们不关心的列。
  3. Attach(...) 那个"部分正确"的实体
  4. 对实体做所需的更改
  5. SaveChanges(...)

这样我们就可以在不实例化我们不感兴趣的属性的情况下更改我们的实体。

下面是一些可以完成上述工作的代码:

 1 // Project just the columns we need 
2 var myPosts = from post in ctx.Posts
3 orderby post.Created descending
4 select new {post.ID, post.Title};
5 // Fabricate new Entities in memory.
6 // Notice the use of AsEnumerable() to separate the in db query
7 // from the LINQ to Objects construction of Post entities.
8 var fabricatedPosts = from p in myPosts.AsEnumerable()
9 select new Post{ID = p.ID, Title = post.Title};
10 // Now we attach the posts
11 // And call a method to modify the Title
12 foreach(var p in fabricatedPosts)
13 {
14 ctx.AttachTo("Posts", p);
15 p.Title = ChangeTitle(p.Title);
16 }
17 ctx.SaveChanges();

注意我们只检索了ID属性(主键)和Title属性(我们要修改的东西),但我们仍成功地进行了更新。

TA DA!

 

*警告/并发问题

    如果你使用存储过程更新实体,这个提示中的内容不适用

    如果你考虑存储过程工作的方式你可以明白为什么。当使用存储过程进行更新时,所有当前值(及部分原始值)被映射到参数,而不管它们是被修改过。这基本意味着你不得不获得所有原始值:(

    另外有些时候你需要告诉Entity Framework一些其它的原始值,因为没有它们更新不会成功:

  • 并发属性:并发属性的原始值被包含在更新语句中,来保证你只可以在明确知道当前数据库版本的情况下更新数据库。所以没有正确的原始值更新不会成功。
  • EntityFramework的EntityKey值:你也需要知道0..1关系的原始值,即使不准备改变此关系。例如如果一个订单有一个客户,你将需要知道CustomerReference.EntityKey,然后你可以使用这个已确立的关系初始化一个新的Order。当你使用FK属性(在即将到来的.NET 4.0中)时,这个问题将不复存在。

C-Side映射条件引用的属性:C-Side映射条件的值用于算出应用了哪个映射,所以没有正确的原始值就不会确立正确的更新映射。大部分人不会使用这个特性。


推荐阅读
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 提升 Kubernetes 集群管理效率的七大专业工具
    Kubernetes 在云原生环境中的应用日益广泛,然而集群管理的复杂性也随之增加。为了提高管理效率,本文推荐了七款专业工具,这些工具不仅能够简化日常操作,还能提升系统的稳定性和安全性。从自动化部署到监控和故障排查,这些工具覆盖了集群管理的各个方面,帮助管理员更好地应对挑战。 ... [详细]
  • 阿里巴巴终面技术挑战:如何利用 UDP 实现 TCP 功能?
    在阿里巴巴的技术面试中,技术总监曾提出一道关于如何利用 UDP 实现 TCP 功能的问题。当时回答得不够理想,因此事后进行了详细总结。通过与总监的进一步交流,了解到这是一道常见的阿里面试题。面试官的主要目的是考察应聘者对 UDP 和 TCP 在原理上的差异的理解,以及如何通过 UDP 实现类似 TCP 的可靠传输机制。 ... [详细]
  • B站服务器故障影响豆瓣评分?别担心,阿里巴巴架构师分享预防策略与技术方案
    13日晚上,在视频观看高峰时段,B站出现了服务器故障,引发网友在各大平台上的广泛吐槽。这一事件导致了连锁反应,大量用户纷纷涌入A站、豆瓣和晋江等平台,给这些网站带来了突如其来的流量压力。为了防止类似问题的发生,阿里巴巴架构师分享了一系列预防策略和技术方案,包括负载均衡、弹性伸缩和容灾备份等措施,以确保系统的稳定性和可靠性。 ... [详细]
  • 全面解析JavaScript代码注释技巧与标准规范
    在Web前端开发中,JavaScript代码的可读性和维护性至关重要。本文将详细介绍如何有效地使用注释来提高代码的可读性,并探讨JavaScript代码注释的最佳实践和标准规范。通过合理的注释,开发者可以更好地理解和维护复杂的代码逻辑,提升团队协作效率。 ... [详细]
  • ### 优化后的摘要本学习指南旨在帮助读者全面掌握 Bootstrap 前端框架的核心知识点与实战技巧。内容涵盖基础入门、核心功能和高级应用。第一章通过一个简单的“Hello World”示例,介绍 Bootstrap 的基本用法和快速上手方法。第二章深入探讨 Bootstrap 与 JSP 集成的细节,揭示两者结合的优势和应用场景。第三章则进一步讲解 Bootstrap 的高级特性,如响应式设计和组件定制,为开发者提供全方位的技术支持。 ... [详细]
  • V8不仅是一款著名的八缸发动机,广泛应用于道奇Charger、宾利Continental GT和BossHoss摩托车中。自2008年以来,作为Chromium项目的一部分,V8 JavaScript引擎在性能优化和技术创新方面取得了显著进展。该引擎通过先进的编译技术和高效的垃圾回收机制,显著提升了JavaScript的执行效率,为现代Web应用提供了强大的支持。持续的优化和创新使得V8在处理复杂计算和大规模数据时表现更加出色,成为众多开发者和企业的首选。 ... [详细]
  • 本文深入探讨了NoSQL数据库的四大主要类型:键值对存储、文档存储、列式存储和图数据库。NoSQL(Not Only SQL)是指一系列非关系型数据库系统,它们不依赖于固定模式的数据存储方式,能够灵活处理大规模、高并发的数据需求。键值对存储适用于简单的数据结构;文档存储支持复杂的数据对象;列式存储优化了大数据量的读写性能;而图数据库则擅长处理复杂的关系网络。每种类型的NoSQL数据库都有其独特的优势和应用场景,本文将详细分析它们的特点及应用实例。 ... [详细]
  • 本文介绍了如何利用ObjectMapper实现JSON与JavaBean之间的高效转换。ObjectMapper是Jackson库的核心组件,能够便捷地将Java对象序列化为JSON格式,并支持从JSON、XML以及文件等多种数据源反序列化为Java对象。此外,还探讨了在实际应用中如何优化转换性能,以提升系统整体效率。 ... [详细]
  • 在 Vue 应用开发中,页面状态管理和跨页面数据传递是常见需求。本文将详细介绍 Vue Router 提供的两种有效方式,帮助开发者高效地实现页面间的数据交互与状态同步,同时分享一些最佳实践和注意事项。 ... [详细]
  • 提升Android开发效率:Clean Code的最佳实践与应用
    在Android开发中,提高代码质量和开发效率是至关重要的。本文介绍了如何通过Clean Code的最佳实践来优化Android应用的开发流程。以SQLite数据库操作为例,详细探讨了如何编写高效、可维护的SQL查询语句,并将其结果封装为Java对象。通过遵循这些最佳实践,开发者可以显著提升代码的可读性和可维护性,从而加快开发速度并减少错误。 ... [详细]
  • 在探讨Hibernate框架的高级特性时,缓存机制和懒加载策略是提升数据操作效率的关键要素。缓存策略能够显著减少数据库访问次数,从而提高应用性能,特别是在处理频繁访问的数据时。Hibernate提供了多层次的缓存支持,包括一级缓存和二级缓存,以满足不同场景下的需求。懒加载策略则通过按需加载关联对象,进一步优化了资源利用和响应时间。本文将深入分析这些机制的实现原理及其最佳实践。 ... [详细]
  • CTF竞赛中文件上传技巧与安全绕过方法深入解析
    CTF竞赛中文件上传技巧与安全绕过方法深入解析 ... [详细]
author-avatar
手机用户2602932547
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有