建议翻译
 
其他人的建议:

progress indicator
没有其他建议。
MSDN 杂志 > 主页 > 所有期刊 > 2009 > MSDN Magazine 六月 2009 >  实体 Framework N 层应用程序中避免使用 anti-Patterns
查看内容:  双语对照查看内容: 双语对照
此为机器翻译内容,社区成员可对其进行编辑。我们十分希望您能单击与以下任一句子关联的“编辑”链接,对翻译进行改进。
Entity Framework
Anti-Patterns To Avoid In N-Tier Applications
Daniel Simmons

This article discusses:
  • Understanding n-tier
  • Don't distribute your objects!
  • Custom service or RESTful service?
  • Some n-tier anti-patterns
This article uses the following technologies:
Entity Framework
As a member of the Entity Framework team, I frequently talk to customers about building applications that use the Entity Framework. Probably the topic I get asked about more than anything else is designing n-tier applications. In this article, I will try to set a foundation on which you can build for success in this part of your applications. The majority of the article is devoted to design anti-patterns for n-tier, which are usually the most important issues that I find. This is a topic where there are a lot of options and many issues to consider, so it is important to understand the overall space before making decisions for your particular application. In future articles, I will examine n-tier patterns for success and some of the key APIs and issues specific to the Entity Framework, and provide a sneak peak at features coming in the Microsoft .NET Framework 4 that should make n-tier significantly easier.

Understanding N-tier
Before I dive into the anti-patterns, it is important to have a common understanding of n-tier.
The first point to be clear on is the difference between tiers and layers. A well-designed application will have multiple layers with carefully managed dependencies. Those layers could live in a single tier or be split across multiple tiers. A layer is just an organizational concept in an application, while a tier denotes physical separation or at least a design that will allow physical separation if needed.
Any application that talks to a database has more than one tier unless that database runs in-process, but the application is not called n-tier unless it involves more tiers than just the database and application. Similarly, every ASP.NET application that involves a database is technically n-tier because there is the database, the Web server, and the browser. Unless you introduce Windows Communication Foundation (WCF) or Web services, you would not call that application n-tier since for most purposes the Web server and browser can be thought of as a single client tier. N-tier applications are those that have at a minimum a database tier, a middle tier that exposes a service, and a client tier.
While it does not sound like that big a deal on the surface, it turns out that implementing applications split across multiple tiers is difficult. There are a lot more pitfalls than you think. These pitfalls led Martin Fowler, in his book Patterns of Enterprise Application Architecture (Addison-Wesley, 2002), to make a very strong statement on the subject:
Don't distribute your objects!
Martin calls it the First Law of Distributed Objects.
As with every design rule, though, there are times when the law must be set aside. Your application may have a scalability problem that requires multiple tiers so you can apply more computing resources. Maybe you need to exchange data with a business partner or customer application. It may just be that you have security or infrastructure constraints that divide your application onto multiple computers or prevent one part of your application from talking directly to another part. When you need multiple tiers, you really need them.
While the anti-patterns presented in this article can be applied to a wide range of applications and technologies, the main focus will be creating and consuming custom WCF services that persist data using the Entity Framework.
Not surprisingly, many n-tier anti-patterns are a result of losing focus on the goal of your application. If you do not keep in mind what motivated you to use an n-tier architecture in the first place, or if you neglect critical persistence concerns, then it is all too easy to get in trouble. The following sections will look at some common problems.

Custom Service or RESTful Service?
REST, or Representational State Transfer, is a type of Web service that is rapidly gaining in popularity. So you might ask yourself what the difference is between RESTful services and custom Web services, and why you might choose one type over the other. The key difference between the two types is that REST services are resource-centric while custom services are operation-centric. With REST, you divide your data into resources, give each resource a URL, and implement standard operations on those resources that allow creation, retrieval, update, and deletion (CRUD). With custom services, you can implement any arbitrary method, which means that the focus is on the operations rather than the resources, and those operations can be tailored to the specific needs of your application.
Some services fit very naturally into the REST model—usually when the resources are obvious and much of the service involves management of those resources. Exchange Server, for instance, has a REST API for organizing e-mail and calendar items. Similarly, there are photo-sharing Web sites on the Internet that expose REST APIs. In other cases, the services less clearly match REST operations, but can still be made to fit. Sending e-mail, for example, can be accomplished by adding a resource to an outbox folder. This is not the way you would most naturally think about sending e-mail, but it is not too much of a stretch.
In other cases, though, REST operations just do not fit well. Creating a resource in order to initiate a workflow that drives monthly payroll check printing, for example, would be much less natural than having a specific method for that purpose.
If you can fit your service into the constraints of REST, doing so will buy you a lot of advantages. ADO.NET Data Services in combination with the Entity Framework makes it easy to create both RESTful services and clients to work with them. The framework can provide more functionality to RESTful services automatically because the services are constrained to follow a specific pattern. In addition, RESTful services have a very broad reach because they are so simple and interoperable. They work especially well when you do not know in advance who the clients might be. Finally, REST can be made to scale to handle very large volumes of operations.
For many applications, the constraints of REST are just too much. Sometimes the domain does not divide clearly into a single pattern of resources or the operations involve multiple resources at once. Sometimes the user actions and business logic around them do not map well to RESTful operations, or more precise control is required than can fit into those operations. In these cases, custom services are the way to go.
You can always build an application that has a mix of REST and custom services. Often the ideal solution for an application is a mixture of both.
Anti-Pattern #1: Tight Coupling
Chances are you have heard about the evils of tight coupling. So you always strive to keep your components as loosely coupled as possible, right? Yeah, right.
Loose coupling is more difficult than tight coupling, and often the performance is not as good. You start off with the best of intentions, but end up asking if the benefit is worth the cost. Why introduce an interface and dependency injection when you could just create an instance of the class and call the method directly? Why build an abstraction with custom objects mapped to the database instead of filling a DataTable and passing it around?
To make matters worse, you do not usually feel the pain of tight coupling until much later. In the short term, you gain some efficiency and get the job done, but in the long run evolving the application can become almost impossible.
If you have been building software for any time at all, you probably understand coupling tradeoffs fairly well when it comes to modules within a tier. When you have modules that work together closely, sometimes tight coupling is the right choice, but in other cases, components need to be kept at arm's length from one another so that you can contain the ripple effect of changes to the application.
When it comes to splitting parts of your application into separate tiers, the significance of coupling becomes much greater. The reason for this is simple. Tiers do not always change at the same rate. If you have a service that is consumed by many clients and you cannot guarantee all the clients will upgrade on demand, then you better make sure you can change that service without having to change the clients. If not, you will encounter a problem that is sometimes called shearing rates of change. Imagine your application being pulled in two different directions until it is forcefully ripped apart.
The trick is to identify which parts of the application might have different rates of change and which parts are tightly coupled to each other. First, consider the boundary between the database and the mid-tier. As your application grows, there is a good chance you will need to adjust the database to improve performance, and if you ever share the database between multiple applications, there is a very good chance you will want to evolve one application's mid-tier without changing the other one. Fortunately, using the Entity Framework already helps here because its mapping system provides an abstraction between your mid-tier code and the database. The same questions should be considered between the mid-tier and the client.
A particularly common and painful example of this anti-pattern in action is an architecture that uses table adapters to retrieve data from the database and Web services that exchange DataSets with the client. The table adapter moves the data into a DataSet with the same schema (thus tightly coupling the database to the mid-tier) and then the Web service exchanges that same DataSet with the client (thus tightly coupling the mid-tier to the client). This kind of system is easy to create—there are Visual Studio tools that lead you through the process nicely. But if you build a system that way, changes to any part of the system are likely to ripple to all other parts.

Anti-Pattern #2: Assuming Static Requirements
Speaking of changes to the system, sometimes you design around an assumption that requirements will remain static, but there are two cases where changing requirements have an especially significant impact. One comes from treating the client as trusted, and the other occurs when the mid-tier service assumes the client will be implemented using a particular technology.
While it is unlikely that trust boundaries will change unexpectedly, when it comes to data integrity, security, and trust, the consequences of getting it wrong are just too great. If you perform validation only on the client, for instance, and on the mid-tier trust that the data you receive is OK to send directly to the database without revalidating, the chance that something will eventually go wrong is much larger than you might think. Even knowing the service only runs within your intranet is not enough to keep your information safe. Someone might create another client using the same service or modify the first client to call the service from a different code path that skips validation. Who knows what might happen.
Further, once you have a service, it is more likely that regular code to be used in ways that you did not anticipate than—so much so that the generally accepted wisdom is that you should always validate and enforce some degree of security on the mid-tier even though that may mean validating or performing access control more than once.
The second issue, locking the client into a particular technology, is even more likely to be a problem. Technologies always change. If an application survives long enough, something will happen that forces technology adjustments, and clients are especially susceptible. You may initially design your application as a rich client desktop application and then later find you need to move it to a mobile phone or Silverlight. If that were the case, and you designed your service to exchange DataSets, then major surgery would be needed for the service and all existing clients.

Anti-Pattern #3: Mishandled Concurrency
While there is a tight coupling downside to exchanging DataSets, concurrency is a complex-but-important area that the DataSet handles well. Unfortunately many developers do not understand the nuances of managing concurrency, and to make things worse, a mistake with concurrency is the kind of problem that often only shows up once the application is in production. If you are lucky, it will manifest as an obvious failure. If not, it may cause corruption to your data over a long period of time without being detected.
At its core, concurrency management is fairly simple: guarantee data integrity even if two clients try to modify the same data at roughly the same time. Particularly attentive readers will note that these problems also come up in cases that are unrelated to n-tier, but concurrency issues are particularly relevant to the Entity Framework n-tier designs, because the Entity Framework's handling of n-tier scenarios creates unique concurrency challenges.
For most applications, the concurrency management technique of choice is optimistic concurrency. Even though many clients may access the database simultaneously, the number of times when the exact same entity is modified in conflicting ways is quite small. So you assume everything will work out, but take steps to detect if something goes wrong.
Detection is driven by one or more properties, collectively called the concurrency token, that change whenever any part of the entity changes. When the application reads an entity, it saves the value of the concurrency token. Later, when it wants to write that entity back to the database, it first checks to make sure that the value of the concurrency token in the database is the same now as it was when the entity was originally read. If it is, the update proceeds. If not, the update halts and throws an exception.
The Entity Framework supports optimistic concurrency by transparently tracking the original value of concurrency tokens when entities are queried and checking for conflicts prior to database updates. The problem with n-tier applications is that this process works transparently only as long as a single ObjectContext instance is used to track the entity from the time it is queried until the time SaveChanges is called. If you serialize entities from one tier to another, the recommended pattern is to keep the context around on the mid-tier only long enough for a single service method call. Subsequent calls will spin up a new instance of the context to complete each task. (Creating a new context instance for every service operation is an important recommendation in its own right, by the way. For more information, see Anti-Pattern #4: Stateful Services.)
Once developers begin to learn how the Entity Framework APIs work for this kind of disconnected operation—disconnected in the sense that the entities are disconnected from the context after the query, sent to another tier, and then re-connected when it is time to save—there is a tendency to fall into a nasty pattern:
  1. Query the entity and serialize it to the client. At this point, the concurrency token's current value is the same as the original value, and that is the only value sent to the client.
  2. The client receives the entity, makes changes, and sends a modified version of the entity back to the mid-tier.
  3. Since neither the client nor the service explicitly kept track of the concurrency token or what properties have been modified, the service queries the database to get the current state of the entity into a newly created context, then compares values between the current entity from the database and the one sent back from the client.
  4. The service calls SaveChanges, which performs optimistic concurrency checks while persisting the changes.
Did you see the problem? Actually there are two problems.
First, every time an entity is updated, it has to be read from the database twice—once when it is first queried and a second time right before the update—which creates a significant extra load on the system.
Second, and more importantly, the "original value" used by the Entity Framework to check if the entity has been modified in the database comes from the second query instead of the first one. That is, it comes from the query that happens right before the update. So the result is that the optimistic concurrency check made by the Entity Framework will almost never fail. If someone else modifies the entity between the first query and the second one, the system will not detect the conflict because the value used for the concurrency check is from after the other client's modification instead of before it.
There is still a small window (between the second query and the update) when the optimistic concurrency check could detect a problem, so you still have to write your program to handle the exception, but you will not really have protected your data from corruption.
The correct pattern is either to make a copy of the entity on the client and send back both the original version unmodified and the modified version or to write the client in such a way that it does not modify the concurrency token. If the concurrency token is updated by a server trigger or automatically because it is a row version number (probably the best plan anyway), then there is no reason to modify it on the client. The current value of the property can be left untouched and used as storage for the original value.
This is a reasonably sound approach because if a bug in the client causes the value to accidentally be modified, it is highly unlikely that you will get a false success. That bug might cause you to get a false failure, but that is much more acceptable than false success.
To make this approach work, when the mid-tier receives the entity from client, you need to attach it to the context and then go over its properties, manually marking them as modified. In either case, though, you will fix both of the problems with the anti-pattern at once. You will no longer query the database twice, and the concurrency check will be based on the correct value of the token (from the initial query) rather than some later value.

Anti-Pattern #4: Stateful Services
Given the comparative ease of developing client-server solutions, the next anti-pattern comes up when developers try to simplify things by keeping the context around across multiple service calls. This seems nice at first because it sidesteps the concurrency problems. If you keep the context alive on the mid-tier, then it will contain the correct original entity values. When you receive an entity back from the client, you can compare the updated entity with the version of the entity in the context and apply changes as appropriate. When you save the entity, the correct concurrency check will be made and no extra database query is required.
While this approach seems easy on the surface, there are a number of problems lurking. Managing the context lifetime can get tricky quickly. When you have multiple clients calling the services, you have to maintain a separate context for each client or risk collisions between them. And even if you solve those issues, you will end up with major scalability problems.
These scalability problems are not only the result of tying up server resources for every client. In addition you will have to guard against the possibility that a client might start a unit of work, but never complete it, by creating an expiration scheme. Further, if you decide that you need to scale your solution out by introducing a farm with multiple mid-tier server, then you will have to maintain session affinity to keep a client associated with the same server where the unit of work began.
A lot of effort and specialized technology has been expended on addressing these issues when, in fact, the best solution is to avoid them altogether by keeping your mid-tier service implementations stateless. Each time a service call is made, the mid-tier should spin up the necessary resources, handle the call, and then release all resources specific to that call. If some information needs to be maintained for a unit of work that extends across multiple service calls, then that information should be maintained by the client rather than the mid-tier so there is no session affinity, no need to expire unfinished units of work, and no server resources in use for a particular client in between service calls.

Anti-Pattern #5: Two Tiers Pretending to be Three
Another anti-pattern I encounter fairly often is also an attempt to simplify this process. Usually it shows up as a request something like, "Why can't you make the Entity Framework serialize queries across tiers?" followed almost immediately by, "Oh, and while you are at it, can you support initiating updates from another tier as well?"
These are probably features Microsoft could add to the Entity Framework, but if you stop and think about them for a minute in light of the other issues I have discussed, you would have to question whether this is a good idea.
If you could create an Entity Framework ObjectContext on the client tier, execute any Entity Framework query to load entities into that context, modify those entities, and then have SaveChanges push an update from the client through the mid-tier to the database server—if you could do all that, then why have the mid-tier at all? Why not just expose the database directly?
Remember Fowler's First Law of Distributed Objects. Keep in mind that the only time this kind of architecture makes sense is when you really, really need it. If you really need it, then you need better security or the ability to scale out with multiple servers, or some other thing that I suggest will not really be solved by introducing a mid-tier that is simply a thin proxy for the database. You might use this technique to subvert a restriction placed on you by a corporate policy, but it is hardly capturing the spirit of an n-tier application. My suggestion is to either invest in building an n-tier application to meet a particular need or, if you can get away with it, avoid n-tier altogether.

Anti-Pattern #6: Undervaluing Simplicity
This brings me to the last n-tier anti-pattern. In the name of avoiding all the anti-patterns discussed previously, it is easy to decide that you need to create the most carefully architected, multi-tier, fully separated, re-validating, super design that you can come up with. Then you can spend all your time building infrastructure and none of your time actually delivering value to your users.
It is important to think over your goals and consider whether you are going to need the investment n-tier requires. Simple is good. Sometimes a two-tier app is just the thing. Sometimes you need more tiers than that, but everything is under your control and trusted or you have an AJAX, Silverlight, or click-once client that auto-deploys so that you do not have to worry about shearing rates of change.
If you can make the problem simpler, do so. Put in all the effort for the full solution if you must, but by the same token make sure you put in enough effort to do the job in a way that meets your goals.

Danny Simmons is dev manager for the Entity Framework team at Microsoft. You can read more of his thoughts on the Entity Framework and other subjects at blogs.msdn.com/dsimmons/ .

实体框架
anti-Patterns 到避免在 N 层应用程序
Daniel Simmons

本文讨论:
  • 了解 n 物理) 层
  • 不要分发您的对象 !
  • 自定义服务或 RESTful 服务?
  • 某些 n 物理) 层反模式
本文涉及以下技术:
实体框架
为此实体的成员 Framework 团队,我经常与客户有关生成使用实体框架的应用程序。 可能该主题我获取询问超过任何其他操作设计 n 物理) 层应用程序。 此文章中, 我将尝试设置的基础您可以生成您的应用程序的这一部分中的成功。 相应的大多数被专门用于设计反模式的 n 层通常是我发现的最重要问题。 这是一个主题有大量选项和许多问题要考虑,这样很重要在为特定应用程序的决定之前了解整个空间。 以后的文章中我将检查 n 物理) 层模式成功和某些关键的 API 和与实体框架,问题并提供传入 Microsoft.NET Framework 4,应使 n 物理) 层得更容易的功能的 sneak 峰值。

了解 N 层
深入讨论,反模式之前,至关重要常见了解 n 物理) 层。
要清除在第一点是在差异层和图层。 一个设计良好的应用程序需要通过仔细管理的相关性的多层。 这些图层未能居住在一个层或跨多个物理层拆分。 一层表示物理分离或允许物理分离必要的至少一个设计时,图层是一个应用程序中, 只是组织的概念。
如果该数据库运行的进程,但除非它涉及到比只是数据库和应用程序的更多层应用程序未调用 n 物理) 层,任何应用程序讨论到数据库的将具有多个层。 同样,涉及数据库的每个 ASP.NET 应用程序是从技术上讲 n 物理) 层,因为将数据库、 在 Web 服务器和浏览器。 除非您引入 Windows Communication Foundation (WCF) 或 Web 服务,因为大多数情况下在 Web 服务器和浏览器可以被视为单个客户端层不会调用该应用程序 n 层。 N 层应用程序是至少具有数据库层,提供了一种服务在中间层和客户端层。
尽管它确实不声音类似的大有问题表面上,证明实现分布于多个层的应用程序很难。 有比您料想的更多缺陷。 这些缺陷导致 Martin Fowler,企业应用程序体系结构 (Addison-Wesley,2002),可在非常强的语句主题的他簿模式:
不要分发您的对象 !
Martin 调用该分布式对象第一个法则。
与每个设计规则但,有时必须留出设置法律的时间。 您的应用程序可能会遇到需要多个层,以便您可以应用更多的计算资源的可伸缩性问题。 也许您需要与业务合作伙伴或客户应用程序的 Exchange 数据。 它可能只是您具有安全或划分到多台计算机应用程序,或阻止应用程序直接与其他部件的一部分的基础结构约束。 时需要多个层,您真正需要它们。
而在本文中介绍的反模式可用于各种应用程序和技术,主焦点将被创建并使用保留数据使用实体框架的自定义 WCF 服务。
毫无疑问,许多 n 物理) 层反模式是失去焦点在应用程序的目标的结果。 如果您不保留记住内容目前可以首先,使用 n 物理) 层体系结构或忽略关键的持久性问题,然后很都太容易来获取问题。 以下各节将介绍一些常见问题。

自定义的服务或 RESTful 服务?
REST,或 Representational 状态传输,是一种快速获得普及中的 Web 服务。 因此这样一您将可能自问一下不同之处是 RESTful 服务和自定义的 Web 服务之间以及为什么可以通过其他选择一种类型。 两种类型关键区别是 REST 等服务是资源中心,这是自定义服务时操作的中心。 与 REST,您将数据划分为资源提供一个的 URL 的每个资源,实现对这些资源,以便创建、 检索、 更新和删除 (CRUD) 的标准操作。 自定义查服务可以实现可以为应用程序的特定的需要定制焦点位于操作,而该的资源和那些操作这意味着任何任意方法。
某些服务很自然适合 REST 模型通常时资源明显并且许多服务包括这些资源进行管理。 Exchange 服务器中,对于是实例会使用为组织电子邮件和日历项的一个 REST API。 同样,没有照片共享网站在 Internet 上公开 REST API 的。 在其他的情况下这些服务不清楚地匹配 REST 等操作,而仍可以适应。 发送电子邮件,是例如可以完成通过将资源添加到的发件箱文件夹。 这不是您最自然会对发送电子邮件,方法但它不过多的一个扩展。
在其他的情况下但,REST 等操作就不适合和。 为了启动驱动器每月的工作流创建资源工资例如检查打印,将为得自然比用于此用途的特定方法。
如果您的服务可以容纳的 REST 限制,这因此将购买您很多优点。 ADO.NET 数据服务结合实体框架可以轻松创建 RESTful 服务和客户端使用它们。 框架可以提供多个功能 RESTful 服务自动因为服务的限制遵循特定的模式。 此外,RESTful 服务有非常广泛的访问,因为它们是这样简单和互操作性。 它们正常尤其是当您不知道提前谁客户端可能是。 最后,REST 等进行缩放以处理非常大的卷的操作。
对于很多的应用程序的 REST 限制是只是太多。 有时域不划分清晰的资源的单个模式或操作同时涉及多个资源。 有时用户操作和周围的业务逻辑不会映射很好地 RESTful 操作,更精确地控制需要或比可以容纳这些操作。 在这的种情况下自定义服务是该方法转。
您始终可以生成具有多种 REST 和自定义的服务的应用程序。 通常,应用程序的理想的解决方案是两者的混合。
anti-Pattern # 1: 紧耦合
很听说的紧耦合 evils。 因此总是尽力使您的组件松散耦合尽可能,权限? 是的右。
松散耦合比紧密耦合,困难并且通常不为好性能。 您最好的意图,从开始,但最终要求是否好处值得成本。 可以只创建类的一个实例和直接调用该方法时,为什么引入接口和依赖项的注入? 为什么使用映射到的填充 DataTable,并将其周围传而不是数据库的自定义对象生成抽象?
更糟糕的是使问题,您不通常认为,获得认可直到许多更高版本的紧耦合。 在短期您获得一些效率,并获得完成,任务,但从长远发展应用程序会变得几乎不可能。
如果您已被构建软件的任何时候在是所有您可能了解耦合折衷方案相当,和谈到层中的模块。 当密切配合的模块有时紧密耦合是正确的选择但在其他的情况下组件需要保留彼此的分支的长度,以便可以包含应用程序的更改的涟漪效果。
应用程序的拆分部分涉及到单独的层,耦合的重要性成为得多。 为此原因是简单的。 层不要始终更改相同的速率。 如果您有一个由多个客户端服务,并且不能保证所有客户端将升级根据,然后您更好地确保您可以更改该服务,而不必更改客户端。 如果不是,您遇到问题有时称为的剪切的更改的速率。 假设应用程序被提取在两个不同的方向,直到它已强制翻录分开。
该技巧就是更改的确定应用程序的部分可能具有不同的比率和哪些部分更改的紧密耦合相互。 首先,考虑数据库和在 mid-tier 之间边界。 随着您的应用程序的增长没有很可能需要调整数据库以提高性能,并且如果曾经共享多个应用程序之间数据库没有您需要一个应用程序 mid-tier 发展而不更改另一个很好的机会。 幸运的是,使用该实体框架已经有助于此处因为其映射系统提供了抽象 mid-tier 代码和数据库之间。 相同的问题应考虑在 mid-tier 和客户端之间。
此操作中的反模式一个特别常见和痛苦示例是使用的表适配器从数据库和 Web 服务检索数据,与客户端的 Exchange DataSet 的体系结构。 表适配器移到 DataSet 的数据具有相同的架构 (因此紧密离合器数据库以在 mid-tier) 并 Web 服务然后交换该相同的数据集与客户端 (因此紧密离合器在 mid-tier 向客户端)。 这种系统是易于创建,有很好地将您引导完成过程的 Visual Studio Tools。 但如果这种方式构建系统对系统的任何部分都可能 ripple 到所有其他部件。

anti-Pattern # 2: 假设静态的要求
更改说到系统,有时您设计围绕假设要求将保留静态,但在不断变化的需求具有尤其重要影响的两种情况。 一个来自为可信,将客户端,并且其他发生 mid-tier 服务假定客户端将使用特定技术实现时。
虽然不可能的信任边界将意外更改,数据完整性、 安全和信任时,但获取错误的结果是只是太大。 如果仅在客户端,对于是实例和 mid-tier 信任您收到的数据是确定将直接发送到数据库而不 revalidating 上执行验证,内容将最终进入错误的机会大于更可能会认为。 甚至知道服务仅运行您的 Intranet 内不足以保存您的信息安全的地方。 有人可能会创建使用相同的服务的另一个客户端,或修改第一个客户端能够从不同的代码路径跳过验证的调用服务中。 谁知道可能会发生。
进一步后一种服务, 它是很可能该正则代码在没有不比预计的方式中使用,如此多通常接受的智慧以便在应当始终验证,,即使这可能意味着验证或执行访问控制多个强制某种程度的安全性,mid-tier。
第二个客户端锁定到特定的技术问题很有问题更可能。 技术随时更改。 如果应用程序都足够长,内容会发生,强制技术的调整,并客户端是非常容易。 您可以最初设计为丰富客户端桌面应用程序应用程序,然后以后发现需要将其移动到移动电话或 Silverlight。 如果该案例,设计 Exchange 数据集将服务,然后主要 surgery 需要为服务和所有现有的客户端。

anti-Pattern # 3: Mishandled 并发操作
虽然有一个紧耦合缺点交换数据集并发将是数据集处理和复杂,但的重要区域。 遗憾的是许多开发人员不理解管理并发,nuances,将事情更糟糕的并发错误的通常只显示应用程序后在生产中的问题类型。 如果幸运的话它将表现为了明显的错误。 如果不是,不被检测到都会导致损坏您的数据长期的时间。
在其核心,并发管理是相当简单: 保证数据的完整性,即使如果两个客户端尝试大致同时修改同一数据。 特别是 attentive 读者会注意这些问题还提出与 n 层无关的情况下,但并发问题是特别是与该实体框架 n 物理) 层设计的因为的 n 物理) 层方案的实体框架的处理创建唯一的并发性挑战。
大多数的应用程序并发操作选择的管理技术是开放式并发。 即使多个客户端可能会同时访问数据库,则完全相同的实体冲突的方式的修改时的次数是非常小的。 这样您假设所有内容将出,工作,但采取措施来检测如果出现问题。
检测推动由一个或多个属性,统称为该并发令牌,更改该实体的任何部分更改时。 在应用程序读取实体,它将保存并发标记的值。 以后,当它希望将该实体写回到该数据库,它首先检查以确保在数据库中的并发标记的值是相同现在它时最初读取该实体。 如果有,更新将继续。 如果,不更新将停止并引发异常。
实体框架通过查询实体透明地跟踪并发操作令牌与原始值,并检查存在冲突的数据库更新才能支持开放式并发。 使用 n 物理) 层应用程序问题是此过程正常透明地仅为长单个 ObjectContext 实例用于跟踪从该查询直到调用 SaveChanges 时该实体。 如果您序列化到另一层中的实体,推荐的模式是只足够长对于单个的服务方法调用 mid-tier 保留周围上下文。 后续调用将旋转上下文完成每个任务的一个新实例。 (创建新的上下文实例,每个服务操作是一个重要建议本身就,顺便。 详细信息,请参阅反模式 4: 状态的服务)
一旦开发人员开始若要了解如何实体 Framework API 用于这种断开连接操作,断开连接的实体会断开连接从上下文在的查询发送到另一层,并且然后 re-connected 时间来保存时,没有一个倾向分为一个棘手的模式:
  1. 查询该实体,和其序列化到客户端。 到目前为止并发标记的当前值与在原始的值相同的唯一值发送到客户端。
  2. 客户端接收该实体、 更改,和将实体的修改的版本发送回该 mid-tier。
  3. 由于客户端和服务都不明确保持跟踪的并发操作标记,或有哪些属性已被修改,服务查询数据库以获取到一个新创建的上下文的实体的当前状态然后比较数据库中的当前实体和恢复从客户端发送一个之间的值。
  4. 在服务调用后者将执行的 SaveChanges 开放式并发检查同时保留所做的更改。
未看到此问题? 实际上有两个问题。
首先,每次更新实体,它必须从数据库中读取两次,一次时它第一次查询,在更新前的第二个时间右在系统上创建大量的额外负载。
第二个,和更是重要在"原始值"实体框架用于检查是否已在数据库中被修改实体来自而不是第一个第二个查询。 这就是它来自于发生更新之前的权限的查询。 因此结果是开放式并发检查由实体框架将几乎永远不会失败。 如果其他人修改第一个查询和第二个之间实体,系统将不检测冲突,因为值用于并发检查是从之前的其他客户端的修改而不是后。
没有仍第二个查询和更新) 之间的小窗口开放式并发检查检测问题,因此您仍然必须编写您的程序来处理该的异常,但您将不真正具有防止数据损坏。
正确的模式是使客户端上的该实体的副本,并修改原始版本和修改后的版本,请发送回或这样不会修改并发操作令牌中写出客户端。 如果并发令牌更新的服务器触发器或自动因为它是一个行的版本号 (可能是最好计划仍),则没有理由以客户端上对它进行修改。 不变,使用为原始值的存储,可以保留该属性的当前值。
这是一个合理的声音的方法,因为如果客户端中的错误导致将意外修改值,太高会 False 的成功。 该错误可能会导致将为 False 的出现故障但的更比 False 成功。
若要将工作,在 mid-tier 接收来自客户端的实体此方法需要将其附加到上下文,以及然后继续通过它的属性手动将其标记为已修改。 在任何一的种情况下通过,您将解决这两个反模式问题次。 您将无法再查询数据库两次,和并发检查将基于初始查询) 中标记的正确的值而不是某个更高的值。

anti-Pattern 4: 状态服务
开发客户端-服务器解决方案的 comparative 轻松,给定下一个反模式出现当开发人员尝试通过在多个服务调用保持周围上下文简化操作。 这看起来很好最初因为它 sidesteps 并发性问题。 如果您保留上下文活动在 mid-tier,它将包含正确的原始实体值。 当您从客户端回收到实体时, 可以比较与上下文中的该实体的版本更新的实体,并应用适当的更改。 如果保存该实体,则进行正确的并发检查,并没有额外的数据库查询是必需。
虽然这种方法似乎表面上轻松,有许多存在处理的问题。 管理上下文生存期可以快速棘手。 当调用该服务的多个客户端必须维护每个客户端或风险的冲突,它们之间的不同上下文。 即使您解决这些问题,您将结束其最主要的可伸缩性问题。
这些可伸缩性问题不是只占用服务器资源的任何客户端的结果。 还必须以防止可能出现客户端可能启动的工作,单元但永远不会完成,通过创建的过期方案。 进一步,如果您决定,需要您的解决方案出扩展通过引入与多个的 mid-tier 服务器场,则您将不得不维护将与同一个服务器的工作单元的开始位置关联的客户端会话关联。
大量工作和特殊的技术已被扩充在解决这些问题时,实际上,最佳的解决方案是避免这些 altogether 通过保持您 mid-tier 的服务实现无状态。 每次进行服务调用,在 mid-tier 应创建必需的资源,处理呼叫,然后释放特定于该调用的所有资源。 如果某些信息需要的信息应保留通过客户端而不是在 mid-tier,因此没有会话关联,不需要为过期的工时的未完成的单位和没有服务器中的资源使用特定的客户端之间,进行维护的然后扩展在多个的服务调用的工作单元服务调用。

anti-Pattern #5: 两个层装作为三
相当经常遇到的另一个反模式还是尝试简化该过程。 通常它显示为一个请求类似,"为什么不能进行该实体框架序列查询层?"跟几乎可立即,"哦,它时,您可以支持从另一层的启动更新?"
这些可能是 Microsoft 可以将添加到实体框架的功能,但如果您停止并根据的其他问题我讨论了一分钟的考虑它们,您将不得不问题是否这是一个不错的主意。
如果客户端层上,可以创建实体框架 ObjectContext,执行到该上下文中加载实体、 修改这些的实体和则必须向该数据库服务器推从客户端通过该 mid-tier 的更新的 SaveChanges 任何实体框架查询如果可以执行的那么为什么有在 mid-tier 根本? 为什么不只是公开数据库直接?
请记住 Fowler 的第一个法则分布式对象。 请记住,唯一的情况这种体系结构的意义是您真正,真正需要时。 如果您真正需要它,则需要更好的安全性或能力与多个的服务器或某些事,我建议将不真正解决通过引入是只是为数据库的细代理的一个 mid-tier 扩展。 您可能会使用此方法 subvert 放入您在公司的策略的限制,但几乎不捕获 n 物理) 层应用程序的时候。 我建议是在完全生成 n 物理) 层应用程序满足特定需要,或如果可以获得立即与其,避免 n 物理) 层或者投资。

anti-Pattern 6: Undervaluing 简单
这将我最后 n 物理) 层反模式。 避免前面讨论的所有反模式的名称很容易确定您需要创建在 re-validating 最仔细结构式,多层,完全分开,超级可以设置自带的设计。 然后您可以花费基础所有您时间构建结构和任何时间实际上将值传递到您的用户。
务必考虑对您的目标,并考虑是否要需要投资 n 物理) 层需要。 简单是很好。 有时,两层应用程序是只是件事情。 有时您需要比的更多层,但所有受您的控制,受信任或有 AJAX,Silverlight 或单击-一次客户端的自动部署,这样做不需担心剪切的更改的速率。
如果您可以使问题更为简单,执行此操作。 如果必须,但通过相同的 token 确保置于完整的解决方案的所有工作您都置于满足您的目标的作业不足,无法工作。

Danny Simmons 是在 Microsoft 实体 Framework 团队的开发经理。 您可以读取多个实体框架上的他想法和在其他主题 blogs.msdn.com/dsimmons/ .

Page view tracker