478
p DevForce DevForce 2010 Developer Guide

Dev Force 2010 Developers Guide

Embed Size (px)

Citation preview

Page 1: Dev Force 2010 Developers Guide

p

DevForce

DevForce 2010

Developer Guide

Page 2: Dev Force 2010 Developers Guide

2 | P a g e

Contents

DevForce, Enterprise Applications, and the ADO.NET Entity Framework .............................. 7 Introduction ........................................................................................................................................................... 7 The Problem .......................................................................................................................................................... 9 Object Mapping Technology ............................................................................................................................... 10 The Microsoft ADO.NET Entity Framework ...................................................................................................... 11

Using DevForce with the Entity Framework ...................................................................................... 14 Advantages of Using DevForce ........................................................................................................................... 14

DevForce in More Detail ....................................................................................................................... 16 Advantages of Using DevForce (Revisited) ........................................................................................................ 17 More DevForce Advantages ................................................................................................................................ 29

Conclusion .............................................................................................................................................. 37

Hello, DevForce ........................................................................................................................... 38 DevForce Application Architecture - The Big Picture ........................................................................................ 38 DevForce and the ADO.NET EntityModel ......................................................................................................... 40 Your First DevForce Application: a Walk-Through ............................................................................................ 41 Understanding the App.Configs .......................................................................................................................... 71 Monitoring Activity ............................................................................................................................................. 72 Appendix: Listings of Sample App.Config Files ................................................................................................. 74 Appendix: Probing Sequence for the App.Config File ........................................................................................ 75

DevForce Entity Model (EDMX) Designer Enhancements ....................................................... 76

Entity-Model-Level DevForce-Specific Properties ............................................................................. 77

Type-Level DevForce-Specific Properties ........................................................................................... 79

Property-Level DevForce-Specific Properties .................................................................................... 80

Customizing the DevForce Code Generation Template ............................................................. 82

Property Interceptors ................................................................................................................... 87 State of the Release Candidate Documentation ................................................................................................... 88 Code Snippets in This Document ........................................................................................................................ 88 Attribute Interception........................................................................................................................................... 88 Named vs. Unnamed Interceptor Actions ............................................................................................................ 91 Interceptor Chaining and Ordering ...................................................................................................................... 92 Multiple Attributes on a Single Interceptor Action ............................................................................................. 94 Defining an Interceptor to Act Against Multiple Types ...................................................................................... 95 The EntityPropertyNames class ........................................................................................................................... 96 PropertyInterceptorArgs and IPropertyInterceptorArgs ...................................................................................... 96 PropertyInterceptor Attribute Discovery ........................................................................................................... 104 Alternative PropertyInterceptor Attribute Method Signatures ........................................................................... 106 Dynamic Property Interception and the PropertyInterceptorManager. .............................................................. 107 EntityProperties and Property Interceptors ........................................................................................................ 109 PropertyInterceptor Keys ................................................................................................................................... 109 Mechanics of Property Interception ................................................................................................................... 109

Business Object Persistence....................................................................................................... 110 State of the Release Candidate Documentation ................................................................................................. 114 Note: Code Snippets in This Document............................................................................................................. 115

Object Persistence Overview .............................................................................................................. 115 The Big Picture .................................................................................................................................................. 115

Page 3: Dev Force 2010 Developers Guide

3 | P a g e

DevForce and the ADO.NET EntityModel ....................................................................................................... 116 Support for POCOs (Plain Old CLR Objects) ................................................................................................... 118 Persistence Management Capabilities ............................................................................................................... 119

Entity Queries and Entity Navigation ............................................................................................... 126 Entity Queries .................................................................................................................................................... 127 Entity Navigation ............................................................................................................................................... 150 The Null Entity .................................................................................................................................................. 160

Asynchronous Communication with the Business Object Server ................................................... 160 Asynchronous Queries ....................................................................................................................................... 161 IAsyncResult Asynchronous Pattern ................................................................................................................. 163 Asynchronous Fulfillment of Navigation Property Queries .............................................................................. 163 Canceling Pending Operations........................................................................................................................... 164

The EntityListManager ...................................................................................................................... 164

Entity Caching ..................................................................................................................................... 168 All Business Objects are Cached ....................................................................................................................... 168 Queries, Navigation, and the Cache ................................................................................................................... 171 Query Workflow ................................................................................................................................................ 174 Query Strategy ................................................................................................................................................... 176 Span Queries ...................................................................................................................................................... 185 Cached Entity Lifespan...................................................................................................................................... 188 Saving the Cache Locally .................................................................................................................................. 188 The TraceViewer: Watch What Data Is Being Loaded, and How ..................................................................... 189

Creating Business Objects .................................................................................................................. 202 When Not to Create ........................................................................................................................................... 202 The Business Object Create Method ................................................................................................................. 203 Auxiliary Business Object Class Methods ......................................................................................................... 208 Adding and Removing Related Objects using Add() and Remove() ................................................................. 208 Business Object Creation Review ...................................................................................................................... 211

Saving Business Objects ...................................................................................................................... 211 EntityState of an Object ..................................................................................................................................... 211 Undo .................................................................................................................................................................. 212 Validation .......................................................................................................................................................... 212 Temporary Id Fix-up ......................................................................................................................................... 212 Life Cycle Events .............................................................................................................................................. 213 Saves and Transaction Management .................................................................................................................. 215 Re-query After Save .......................................................................................................................................... 216 When Save Fails ................................................................................................................................................ 217 Data Source Concurrency .................................................................................................................................. 219 Saving the “Dependency Graph” ....................................................................................................................... 226 Dependency Graph Retrieval ............................................................................................................................. 229 Workflow For a Save ......................................................................................................................................... 232 Saving the Cache to a Local Disk File ............................................................................................................... 233 XML Serialization of Business Objects ............................................................................................................. 234

Business Object Persistence – Advanced .................................................................................. 237

State of the Release Candidate Documentation ................................................................................ 239

Getting Information About an Entity Type with GetEntityMeta() ................................................ 239

Access Both Local and Remote Data Sources In the Same N-tier Application ............................ 240

Stored Procedure Queries ................................................................................................................... 241 SQL Server Stored Procedure Queries .............................................................................................................. 243

Page 4: Dev Force 2010 Developers Guide

4 | P a g e

Stored Procedure Entity Navigation .................................................................................................. 246

Forced Re-fetch ................................................................................................................................... 246

Lost Connection During Query .......................................................................................................... 248

Query Cache ........................................................................................................................................ 249 EntityManager.RemoveEntities Overload Preserves Query Cache ................................................................... 249

MergeStrategy In More Detail ........................................................................................................... 251

The EntityManager.AttachEntity Method ........................................................................................ 255

Filtering Queries .................................................................................................................................. 258

Query Inversion in More Detail ......................................................................................................... 260

Transactional Queries ......................................................................................................................... 265

DevForce and Data Sources – Deep Dive .......................................................................................... 266 DataSourceKeys, DataSourceKeyResolvers, and DataSourceExtensions ......................................................... 267 EntityManagers and DataSourceExtensions ...................................................................................................... 268 Tenant Extensions .............................................................................................................................................. 271 Multi-Part Extensions ........................................................................................................................................ 271 Extensions and EntityServers ............................................................................................................................ 272 Dynamic DataSourceKeys and the DataSourceKeyResolver ............................................................................ 273

Multiple Application Environments .................................................................................................. 275

Multiple EntityManager Instances .................................................................................................... 276

Multi-Threading in a DevForce App ................................................................................................. 277

Batching Asynchronous Tasks ........................................................................................................... 279

Service Oriented Architecture ........................................................................................................... 281

POCO Support in DevForce ............................................................................................................... 283 Examples of POCO Classes ............................................................................................................................... 284 Examples of a POCO Service Provider Class .................................................................................................... 285 Example of a Client-Side Class Containing Extension Methods for the EntityManager ................................... 288 Obtaining an EntityAspect Property on Your POCO Object ............................................................................. 289 Data Contract Serializer (DCS) versus .NET Data Contract Serializer (NDCS) .............................................. 290 POCO Save mechanisms ................................................................................................................................... 293 Summary – Things to Remember When Using POCOs in Your DevForce App .............................................. 298

Validation Through Verification ............................................................................................... 299 Introduction ....................................................................................................................................................... 299 DevForce Verification ....................................................................................................................................... 300

Getting Started .................................................................................................................................... 301 Validation-Related Settings In the Entity Data Model Designer ....................................................................... 301 Generated Property Code ................................................................................................................................... 302

A (Very) Brief Overview of Verification Mechanics ........................................................................ 306

VerifierOptions .................................................................................................................................... 306

Verification in the User Interface ...................................................................................................... 311 Verification in WPF and Silverlight .................................................................................................................. 311 Verification and WinForm User Interfaces ........................................................................................................ 311

Validation Through Verification - Advanced ........................................................................... 316

Page 5: Dev Force 2010 Developers Guide

5 | P a g e

Introduction ....................................................................................................................................................... 316

Verification Types Overview .............................................................................................................. 317 Main Verification Classes .................................................................................................................................. 317 Verifiers ............................................................................................................................................................. 319 VerifierResult .................................................................................................................................................... 322 Triggers .............................................................................................................................................................. 324 VerifierEngine ................................................................................................................................................... 326 PropertyValueVerifiers ...................................................................................................................................... 328

Verification Deep Dive ........................................................................................................................ 332 Verifiers ............................................................................................................................................................. 333 Verifier Result ................................................................................................................................................... 338 Triggers .............................................................................................................................................................. 341 VerifierEngine ................................................................................................................................................... 350

Invoking Verification .......................................................................................................................... 355 Instance Verification .......................................................................................................................................... 357 Trigger Verification: BeforeSet and AfterSet .................................................................................................... 358 Monitor Execution with the VerifierBatchInterceptor ....................................................................................... 362

DevForce Silverlight Apps ......................................................................................................... 365 Overview - What is DevForce Silverlight? ........................................................................................................ 365 Creating a DevForce Silverlight Application .................................................................................................... 366 Silverlight Deployment Steps ............................................................................................................................ 366 Questions and Answers...................................................................................................................................... 367 Troubleshooting ................................................................................................................................................. 368

Web Applications........................................................................................................................ 372 State of the Release Candidate Documentation ................................................................................................. 372 Introduction ....................................................................................................................................................... 372 The DevForce ASPDataSource Component ...................................................................................................... 372 Using the ASPDataSource in Development ...................................................................................................... 373 Overridable Methods for Select, Update, Insert, and Delete ............................................................................. 373 The EntityAdapterManager Class ...................................................................................................................... 374 The Configure Data Source Wizard ................................................................................................................... 375 Parameter Collection Editor .............................................................................................................................. 375 Retrieving Schema Information ......................................................................................................................... 376 Third Party Support ........................................................................................................................................... 376

Business Object Server............................................................................................................... 377 Business Object Server Architecture ................................................................................................................. 377 EntityService Startup and Shutdown ................................................................................................................. 381 EntityServer Startup and Shutdown ................................................................................................................... 381 Remote Service Method Call (RSMC) Methods ............................................................................................... 382 Push Notification ............................................................................................................................................... 384

BOS Hosting Details ............................................................................................................................ 385 The DevForce Client ......................................................................................................................................... 389

Vista Setup ........................................................................................................................................... 391 Vista setup requirements for the ServerConsole or ServerService .................................................................... 391 Vista setup requirements for IIS ........................................................................................................................ 391

Troubleshooting ................................................................................................................................... 392 Worked in 2-Tier, Strange Errors in n-Tier ....................................................................................................... 392

Disconnected Applications ......................................................................................................... 394 State of the Release Candidate Documentation ................................................................................................. 394

Page 6: Dev Force 2010 Developers Guide

6 | P a g e

Introduction ....................................................................................................................................................... 394 Running Offline ................................................................................................................................................. 396 Securing Offline Data ........................................................................................................................................ 409

Security ....................................................................................................................................... 416 State of the Release Candidate Documentation ................................................................................................. 416 Introduction ....................................................................................................................................................... 416 Authentication ................................................................................................................................................... 417 Authorization ..................................................................................................................................................... 420 Query and Save Interception ............................................................................................................................. 422 Encryption ......................................................................................................................................................... 425 ASP.NET Security Integration .......................................................................................................................... 425

Deployment ................................................................................................................................. 430

State of the Release Candidate Documentation ................................................................................ 430

Document Overview ............................................................................................................................ 430

DevForce And the App.Config File .................................................................................................... 431 Creating and Editing a Configuration File ......................................................................................................... 431 IdeaBlade DevForce Configuration Editor ........................................................................................................ 432 DevForce Elements in App.Config .................................................................................................................... 433 Configuration File Location .............................................................................................................................. 434 Client and Server Versions of App.Config ........................................................................................................ 435 Probing in DevForce .......................................................................................................................................... 436

EntityServerQueryInterceptor ............................................................................................... 438

Server ......................................................................................................................................... 438

Data Server Deployment ..................................................................................................................... 439

Deploying a DevForce Silverlight Application .................................................................................. 439 Deploying to IIS Version 6 ................................................................................................................................ 439 Deploying to IIS Version 7 ................................................................................................................................ 443 Troubleshooting ................................................................................................................................................. 447 Resources ........................................................................................................................................................... 448

Deploying a DevForce WinClient Application.................................................................................. 448 Overview ........................................................................................................................................................... 448 Deploying a Single-Tier WinClient Application ............................................................................................... 450 Deploying Two-Tier (Client-Server) WinClient Applications .......................................................................... 450 Deploying N-Tier (Smart-Client) Applications ................................................................................................. 450 Building Blocks ................................................................................................................................................. 451

Troubleshooting ......................................................................................................................... 473

State of the Release Candidate Documentation ................................................................................ 473

General Troubleshooting .................................................................................................................... 473

Troubleshooting Silverlight Apps ...................................................................................................... 473

Contacting Support ............................................................................................................................. 476 Identifying your DevForce version .................................................................................................................... 477

Upgrading Your Software .................................................................................................................. 478

Page 7: Dev Force 2010 Developers Guide

7 | P a g e

DevForce, Enterprise Applications, and the

ADO.NET Entity Framework

DevForce, Enterprise Applications, and the ADO.NET Entity Framework .............................. 7

Introduction ........................................................................................................................................................... 7

The Problem .......................................................................................................................................................... 9

Object Mapping Technology ............................................................................................................................... 10

The Microsoft ADO.NET Entity Framework ...................................................................................................... 11

Using DevForce with the Entity Framework ....................................................................................................... 14

Advantages of Using DevForce ........................................................................................................................... 14

DevForce in More Detail ....................................................................................................................................... 16

Advantages of Using DevForce (Revisited) ........................................................................................................ 17

More DevForce Advantages ................................................................................................................................ 29

Conclusion .............................................................................................................................................................. 37

Introduction DevForce is a framework for building and operating multi-tier, data-driven enterprise applications.

By “enterprise application” we do not mean simply a big application, or an application for a big company. Rather,

we refer to an application with the following specific characteristics:

Its users devote many hours to its use, performing tasks essential to conducting the organization‟s business;

It requires a rich and responsive graphical user interface, dense with sophisticated controls;

User interactions are complex; task and context switching is common;

It presents data that are complex in themselves, and deeply interrelated;

The data are stored centrally and shared with other users.

Supply chain, customer relationship management (CRM), and asset-tracking applications are typical

examples.

User productivity is critical. That puts a premium on the application’s ability to provide a highly

responsive, richly featured user experience – the kind of experience typical of a desktop application

running directly on a client machine.

We expect people to get work done at any time from anywhere. Those people may be employees or

they may be valued partners. In either case, security matters. Accordingly, we often need to deploy and

Page 8: Dev Force 2010 Developers Guide

8 | P a g e

operate enterprise applications over a wide area network – preferably over the internet – with

undiminished productivity and security.

DevForce is especially suited to building and running applications that require a rich user experience

delivered to remote, Internet-connected clients.

While DevForce contributes at many levels of the enterprise application architecture stack, its Data

Services, Object Relational Mapping (ORM) technologies, and object-oriented approach to data

management draw most of the attention.

Microsoft has stepped into this arena with the Language Integrated Query (LINQ) and the ADO.NET

Entity Framework. The Entity Framework is a robust ORM solution; the developer can retrieve data as

“entities” by writing “LINQ to Entities” statements in her preferred .NET programming language.

DevForce delegates to the Entity Framework the mapping between object and relational database

schemas, as well as the database persistence operations (queries and saves). These are important and

challenging tasks that the Entity Framework handles well.

There is much more to an application than how it handles raw data. There is the business object layer

that encapsulates the data and governs those data with business rules. There are higher layers that

address the application workflow and user experience. All of this is outside the purview of the Entity

Framework.

If we concentrate only on data management, we still find enterprise application requirements untouched by the

Entity Framework. Chief among them are:

Centralized services, n-tier and Internet support, distributed transactions, performance, security, scalability,

and Silverlight support - needs best met with an intelligent middle-tier server.

Highly responsive client UI‟s that exploit caching to avoid redundant, slow trips across the wire.

Object models mapped to multiple data repositories

Objects mapped to non-relational data sources

Proper support for a business object layer with business rules.

DevForce satisfies these requirements even as it relies on the Entity Framework for basic object mapping and

query facilities. The key components of DevForce include:

the EntityManager, which includes a queryable client-side cache;

the Business Object Server (BOS) for services in a middle tier;

a provider for the LINQ language that permits LINQ queries to be used with both the client-side cache and

remote data sources

object mapping enhancements which extend the Entity Framework designer and generate DevForce entity

code;

business object enhancements such as dynamic validation/verification, localization, object lifecycle events,

binding support, and other extensions that simplify your code.

This chapter explores the key data management issues for .NET enterprise application developers. It

introduces the LINQ and the Entity Framework, explaining what they do and where they leave off. It

then describes how DevForce fills in the critical gaps.

Page 9: Dev Force 2010 Developers Guide

9 | P a g e

The Problem Every business application is an extended dialogue between a user and the business objects that fulfill

the application’s purpose. Those business objects are behavioral objects first and foremost. They are the

embodiment of the customer stories that describe what the application does and how it does it.

A few behaviors may be stateless; financial calculations come to mind. But there is usually data

somewhere in those business objects. An order has a customer and a delivery date and line items

describing quantities of goods sold for a price. There is no escaping the data aspect of business objects

and all of that data must be managed.

While the application is running, the data are held in session in some form. In an object-oriented system

they are held in fields and exposed as properties of a class instance. But because the data are long-lived

– longer-lived than any one session – they have to be saved between sessions. And because we share

our data with others, we have to save the data in permanent storage accessible over a network.

Shuttling data between storage and the application session is one of those necessary but “dirty” jobs, a

job completely unrelated to the application’s purpose.

Developers long ago discovered three data management problems.

First, the way we store data is not the way we use data in an application. Money, for example, is both an

amount and a currency (dollars, euros). The two aspects require separate slots in storage; from the

application perspective, it’s just one thing: money. An “order” in the context of an application session

may be seen as one “thing” with a customer, a shipper, line items, etc. When we store that order in a

relational database, the order, customer, shipper and line are five different things. So the best

representation of stored data often is not the best representation for session data.

Second, session data are governed by rules. We must know the customer for an order before we can

deliver the ordered goods. The date of the order should precede the delivery date. Some other part of

the application may need to be alerted when the order is actually delivered. The application is more

maintainable and easier to understand when the rules (behavior) and the data are bound together as

“business objects” or “entities”. Such rules are largely irrelevant when the data are tucked safely away

in storage.

Third, there are many mechanical matters surrounding saving and retrieving data that have nothing to

do with the application’s purpose such as opening and closing connections, composing SQL, detecting

concurrency violations, converting raw data into Data Transfer Objects, and managing transaction

boundaries. Getting the application dialogue right is hard enough without these distractions. Yes, the

application still has to ask for data and stow them away. But there should be a way to express our intent

simply and entirely in terms of the application entities. Ordinary operations should make no mention of

databases, connections, tables, or columns.

The profound differences between stored data and session data lead developers to expend enormous

energy moving and translating between stored and session representations. This is wasted energy from

Page 10: Dev Force 2010 Developers Guide

10 | P a g e

the perspective of the application customer who could not care less about our implementation

problems.

It is also wasted energy from the developer’s perspective because this problem has been solved by

object mapping technology.

Object Mapping Technology An object mapping technology maintains two views of the data. There is a conceptual model for

representing the data within the entities used by the application and there is a storage model that

defines how the data are stored in the repository. These two models have completely different

characteristics, as we have seen. The conceptual model could include a conceptual order, an order

entity, as it is understood by the application. The storage model describes how the order entity’s data

values are held in the data repository.

If the repository is a relational database, many of the order entity data values – its state – are likely held

in columns of a table. The value of a DeliveryDate property of an Order entity might be stored in the

[DeliveryDt] column of an [OrderHeader] table row. The correspondence between the conceptual

order entity and the table row is obvious and strong in this example. Even so, the correspondence is not

literal; there is Order and DeliveryDate on one side; OrderHeader and DeliveryDt on the other.

Therefore, the object mapping technology maintains a “map” of the correspondence between entities

of the conceptual model and the table rows in the storage model so that it can transform one

representation into the other.

The Order entity has a related Customer entity and related OrderDetail entities. These additional

entities might correspond to Company and OrderLineItem tables in a relational database.

Relational database tables don’t have relationships. They have foreign key constraints that imply these

relationships. Accordingly, the object mapping technology also maintains a map of the associations

between entities and the foreign key constraints in the database. The map records the pairing of the

relationship between Order and Customer with the foreign key constraint between the OrderHeader

and Company tables.

This order example is especially simple. Other mappings could be enormously complex, with values

changing shape (type), entities splitting among multiple tables, and relationships weaving through

intermediate association tables.

Without an object mapping facility, the application developer would have to be constantly aware of

these correspondences as she wrote instructions to retrieve and save application data. Small changes in

the actual storage schema or in the application entity model could easily break the code in a hundred

places.

Page 11: Dev Force 2010 Developers Guide

11 | P a g e

Without an object mapping facility, the application would become vulnerable and brittle as it grew and

aged. Productivity would fall as developers devoted increasing effort to keeping the conceptual and the

storage models aligned.

The Microsoft ADO.NET Entity Framework The Microsoft ADO.NET Entity Framework is quickly becoming the standard for database access in .NET

applications. DevForce builds upon the Entity Framework, so we introduce the Microsoft technology

here before explaining DevForce’s added value.

Read more about the Entity Framework at http://msdn.microsoft.com/en-us/library/bb399572(VS.100).aspx

Entity Data Model (EDM)

The Entity Framework supports an Entity Data Model (EDM) that describes data from the application

perspective.

The EDM does not include the actual business object classes that contain those data; rather it defines

certain of the data and data relationships within those classes in an implementation-agnostic language

of its own.

Concretely, the EDM is an XML schema file that defines a conceptual data model. That schema is

accompanied by two other XML schema files: one describing how the data are stored (the storage

model) and another that maps the conceptual model to the storage model.

The Entity Framework uses this chain of descriptions to move data between the data-laden objects in

memory and the actual data repositories. For this to work at runtime, the conceptual schema (the EDM

proper) refers to entity classes of the application while the storage model gets matched up, via

configuration, with a real database running on a server somewhere.

The Entity Data Model Designer

Most developers prefer to use a tool to work with XML rather than edit XML by hand. EDM XML is dense

and forbidding so a tool is a practical necessity.

The Entity Data Model Designer is a Visual Studio design tool that provides the developer with a

graphical, drag-and-drop EDM design experience. The designer enables simultaneous development of all

three related schemas – the conceptual, storage, and mapping schemas.

Most applications are predicated on a pre-existing database. This database cannot be ignored; the

conceptual model must ultimately come to terms with it. Most developers find it convenient to confront

this fact early and will prefer to generate the conceptual data model and associated schemas using the

Entity Data Model Wizard. The wizard produces the EDM schemas which then can be viewed and edited

in the designer.

Page 12: Dev Force 2010 Developers Guide

12 | P a g e

Entity Object Layer

The Entity Framework business object layer consists of the classes that implement the application

business objects.

The Entity Framework includes an entity class generator that uses the EDM to produce class code that

defines the business object data fields and their accessor properties. It also generates the navigation

properties that enable the application to traverse from one object to its related objects (e.g. from an

order to its customer).

The EDM describes only the business object data and their relationships. The Entity Framework knows

nothing about the business object behavior that applies to the data so there is no business logic in the

generated code. The application developer writes business logic separately in a companion class file. The

two files – the developer’s business logic file and the generated object data management file – combine

to form a single definition of the business object, the business object class.

Technically, each file defines a .NET partial class. The compiler knits the two together, resulting in the

complete business object class.

Entity Persistence

The Entity Framework includes components responsible for moving business object data between the

application and the database.

The ObjectContext is the most visible of the components. The application uses ObjectContext to

retrieve, hold, and save entities. The ObjectContext maintains a cache of all the entities it manages. The

developer writes queries and submits them to the ObjectContext, which retrieves the selected entities

and adds them to its cache before returning them to the caller. The developer creates new objects and

adds them to the ObjectContext. The ObjectContext tracks changes – adds, modifications, deletes – to

entities in its cache. A save command tells the ObjectContext to write the changed entities to the

database.

The Entity Framework handles all of these relational database persistence operations without troubling

the developer with details. The Entity Data Model and a few guiding parameters are all it needs.

LINQ to Entities

Earlier we described three problems for the developer who needs to represent data in the application as

business objects. The third problem was how to retrieve and save business objects using a language that

hid the underlying mechanisms and stayed true to the entity-oriented paradigm.

While the mechanics of saving business object data are challenging, it has never been difficult for

developers to express their intent. It is usually sufficient to tell some service class to “save” and the

service knows what to do.

Getting data is a different story. It is not easy to say precisely which data you want, and in what form,

using a general purpose programming language. It’s harder still to write queries in a strongly-typed

Page 13: Dev Force 2010 Developers Guide

13 | P a g e

manner and stay within an entity-oriented paradigm. Until recently, object mapping vendors offered

their own “object query languages” (OQLs) which were, in fact, merely special purpose classes with

strangled interfaces. OQL queries were clumsy to write and repugnant to read.

With its release of the .NET 3.5 Framework, Microsoft added new language facilities for finding and

accessing data in a general purpose, object-oriented way, without exposing the details of data storage

and retrieval. Chief among the new features is LINQ, an abbreviation of Language Integrated Query.

A LINQ query looks much like an SQL query. Most programmers have long experience with SQL so, while

SQL itself may be tortured, most programmers are accustomed to it and find LINQ expressions familiar:

C#

IQueryable<Product> products =

from prod in anObjectContext.Products

where prod.ReorderLevel > 100

select prod;

foreach (Product aProduct in products) {…}

VB

LINQ defines a set of query operators for interrogating arbitrary sources of data. Anything that can be

enumerated can be queried with a LINQ expression. We can use LINQ to select items from a list, nodes

from an XML file, file names from a file folder, or records from a database.

LINQ itself does not know how to do any of these things. LINQ defines the query operators and patterns

for writing query expressions. The operators and expressions are meaningless until they are married to

an implementation that is specific to a domain. Thus there is a LINQ implementation for querying in-

memory objects (LINQ to Objects), an implementation for querying XML structures (LINQ to XML), an

implementation for querying relational databases (LINQ to SQL), and so on. Microsoft provides some of

these implementations but third parties can develop their own and Microsoft encourages them to do so.

The LINQ facility provides the expressiveness we need for querying entities. What we need is a LINQ

implementation that supports an object mapping technology. Microsoft’s LINQ to Entities is that

implementation for the Entity Framework.

Entity SQL

The Entity Framework supplements LINQ to Entities with its own query language called Entity SQL. Entity

SQL is a storage-independent dialect of SQL that works directly with the conceptual model. An Entity

SQL query refers to entities, properties, and associations (e.g. Order and Order_Customer) rather than

the database elements in the storage model. The particulars of data storage remain hidden in the

object-oriented data design.

Page 14: Dev Force 2010 Developers Guide

14 | P a g e

Entity SQL queries are strings as seen in this example:

C#

string queryString =

@"SELECT VALUE Product FROM Products “ +

AS Product WHERE Product.ReorderLevel > 100";

ObjectQuery<Product> products =

new ObjectQuery<Product>(queryString, anObjectContext);

foreach (Product result in products) {…}

VB

One significant drawback: Visual Studio will not detect even simple mistakes because the query string

won’t be evaluated until runtime.

Using DevForce with the Entity Framework

Microsoft’s Entity Framework is a solid foundation for object relational mapping and relational database

persistence operations. LINQ to Entities is a huge advance over SQL string commands and proprietary

object query languages. We covered this same territory in our earlier, .NET 2.0 version of DevForce; we

are pleased now turn over some of these responsibilities to the Entity Framework.

DevForce integrates directly into the Entity Framework‟s editor in Visual Studio and provides extensions that enable

DevForce specific functionality by augmenting the generated code and by giving the developer more granular

control over the generated class and property code.

DevForce relies upon the Entity Framework for the persistence operations that target relational

databases. The Entity Framework prepares and issues the actual vendor SQL. The Entity Framework

issues all insert, update, and delete commands and employs optimistic concurrency techniques to detect

collisions between updates of the same object by different users.

Advantages of Using DevForce

The Entity Framework does a good job of handling relational database mapping and persistence operations for

client / server applications. However, most enterprise applications need better data management and better support

for developing the business objects that encapsulate the relational data.

DevForce provides essential improvements in such critical areas as:

Infrastructure for n-tier applications

Security

Client application performance

Model design and code generation

Multiple data sources

Intermittently connected and offline apps

We summarize each point in the balance of this section.

Infrastructure for Multi-Tier Applications

Page 15: Dev Force 2010 Developers Guide

15 | P a g e

The Entity Framework only supports a 2-tier architecture in which the client machine speaks directly to a relational

database server. This won‟t work for many enterprise applications, especially those that

Connect to servers over the Internet, a wireless, or a wide area network.

Require rigorous security.

Must scale to support many users, especially external partners and customers.

Offer applications On-Demand (Software-as-a-Service).

Will deploy as a Silverlight application in a browser.

Such applications require the performance, security, and scalability of an intelligent middle tier server

that mediates between client machines and such server-side resources as databases and web services.

DevForce implements an end-to-end, multi-tier (n-tier) architecture whose middle tier component is

called the “Business Object Server” (BOS).

Security The Entity Framework has no intrinsic security features. Because of it two-tier approach, the security

burden falls entirely on the network and the database.

That may be sufficient for simple applications with few users who are always connected within the

company LAN. But we will need a better answer when authentication and authorization schemes

become fine grained and application specific, when the number of users grows, and when some of those

users are reaching in from outside the company walls.

The DevForce n-tier solution supports a rich variety of standard and custom authentication techniques

and provides encryption and authorization points on both client and server.

Client Application Performance Data access is the number one performance killer. Large volumes of data are deadly. Frequent trips to

the server are worse. And it’s really bad if the UI freezes while waiting for data. Responsiveness and user

productivity improve dramatically when we eliminate unnecessary trips, reduce the size of data

traveling over the wire, and retrieve data asynchronously.

None of this is easy to implement. The Entity Framework is a purely 2-tier architecture in which the

client talks SQL to the database, a chatty conversation with few means to shrink the data. It doesn’t

remember previous queries, we can’t query its primitive entity cache, and we can’t query

asynchronously.

A DevForce application deployed in n-tier mode represents business object data in a compact form and

compresses the data before sending it resulting in smaller payloads over the wire. Smaller payloads,

faster app.

Most applications ask for the same data over and over. DevForce has a query-able entity cache and a

query cache. We can ask the entity cache any question, including questions we’ve never asked before.

Page 16: Dev Force 2010 Developers Guide

16 | P a g e

The query cache remembers previous database queries so repeated questions don’t cause redundant

server visits.

In fact, we use DevForce to Entities, a LINQ-based query language, to pose questions that can search the

cache, search the data source, or search both as we wish.

Finally, DevForce offers asynchronous queries that can hide the actual cost of a remote query as

perceived by the end user. The UI continues to function and we can occupy the user’s attention with an

initial set of data while the balance is retrieved in background.

Multiple Data Sources The Entity Framework supports just one database per Entity Data Model. But many application data

models draw from data storied in multiple data sources.

In a supply chain application, orders may be stored in an inventory database while ledger entries are

captured in an accounting database. Orders and ledger entries have keys that, conceptually, enable

navigation between them even though a cross database query is not technically possible.

In DevForce we can define a single model that holds both orders and ledger entries and the code

generator can produce “navigation properties” for seamlessly navigating between them. DevForce

handles the SQL for simulating the cross database “join”.

Order and ledger updates must be saved transactionally. DevForce can perform such distributed

transaction; the Entity Framework, knowing only one database, cannot.

Non-Relational Data Sources The Entity Framework can only map entities to relational databases. DevForce’s POCO (Plain Old CLR

Object) support allows any data source that can be exposed as an enumeration to be used as a data

source, which enables developers to work with a uniform, consistent object mode, regardless of the

backing store.

Intermittently Connected and Offline Applications Entity Framework applications are vulnerable to temporary connection failures. There is no effective

way to recover from a query or save that fails because the connection or server is unavailable. There is

no intrinsic solution to “the airplane problem” – the application that must be able to launch and run

offline as when working while in flight.

DevForce applications have the means to survive transient connectivity and to thrive offline.

DevForce in More Detail

We highlighted the most significant DevForce differences in the previous section. Here we explain them

in greater detail and cover some of the other important DevForce features that improve application

design and developer productivity.

Page 17: Dev Force 2010 Developers Guide

17 | P a g e

Advantages of Using DevForce (Revisited)

Multi-Tier Applications

The Entity Framework is a client / server technology. Its ObjectServices component, which is responsible for

querying and saving data to the database, executes in the same process as the client business object layer. Database

SQL commands and raw data flow over the wire.

This works just fine when there are relatively few clients, all connected to a secure, high speed LAN.

Performance becomes a serious problem when the traffic goes up or when going over a wide area

network. There’s a lot of back-and-forth talk when SQL passes over the network and the data are

verbose. With reduced bandwidth and increased latency, those frequent roundtrips for data that no one

noticed before become serious problems and the user experience slows to a crawl.

Furthermore, in order for a two-tier application to work over the internet, you would have to expose

your database directly to the world. This opens up the possibility of someone stealing the connection

string and browsing or changing your database without authorization.

The DevForce n-Tier Solution

The DevForce n-tier solution, with its “Business Object Server” (BOS) deployed in a middle tier, overcomes all of

these obstacles.

Page 18: Dev Force 2010 Developers Guide

18 | P a g e

The Entity Framework has relocated from the client arena to the Business Object Server where it now

functions purely as an object mapping technology, translating persistent data between entity and

storage representations. The client application hosts the DevForce Entity Manager, a component

responsible for holding business objects in cache and communicating with the BOS.

The business objects and the Entity Manager itself are completely decoupled from the Entity

Framework. There are no references on the client to any of the Entity Framework assemblies.

Nor do clients talk to the database. Instead, the Entity Manager sends commands to the BOS and

receives business objects in return.

Commands may be expressed in a variety of formats including the new LINQ to DevForce query

language. The BOS translates a LINQ to DevForce query into a LINQ to Entities query and submits it to

the Entity Framework. The Entity Framework returns simple entities to the BOS which forwards them to

the client. DevForce on the client turns them into business objects and caches them in the Entity

Manager.

The BOS and the client DevForce Entity Manager exchange data in a serialized binary form that passes

easily through firewalls and over the Internet. The BOS compresses the data before sending them to the

client. These smaller payloads reduce network traffic and improve client performance.

The BOS is effectively stateless. It retains no essential information about client sessions between

requests. Each client request resolves to a method call running on a new thread; the call holds onto

entity data just long enough to fulfill the request after which it is discarded. Such statelessness makes it

easy to distribute requests among multiple BOS servers for scalability and fault tolerance.

Remote Services

Some applications require services that must execute in a centrally hosted environment, perhaps

because they involve proprietary logic or because they crunch volumes of data that would swamp the

network if transmitted to clients. A client can make a “remote service call” to the BOS, which will invoke

custom server side methods to perform or delegate these hosted services.

The BOS can watch for server-side events such as data updates or network notifications, and publish

corresponding events to subscribing clients through its “push” service.

DevForce Silverlight

Microsoft Silverlight enables deployment of .NET applications within a browser. There is no application

to install, no client footprint, and no compromise of the client machine’s security. The door is open to

deliver applications to consumers and locked-down enterprise environments securely.

Data access remains a challenge. Data-driven Silverlight applications need access to the same data as

their desktop equivalents. A Silverlight application can only reach data resources over the Internet and,

as we’ve seen, the Entity Framework cannot move data over the Internet. But a DevForce Silverlight

application can.

Page 19: Dev Force 2010 Developers Guide

19 | P a g e

Also, the same DevForce object model can be used to drive multiple UI front-ends, whether its

Silverlight, WPF, WinForms, or ASP.NET.

In Summary

With the DevForce n-tier capability,

The Entity Framework becomes an n-tier platform

Business object data can travel through firewalls and over the Internet

Data are compressed and encrypted for fast, secure transport

The client can request non-data services to be executed on the server and subscribe to server events.

A software vendor can offer software-as-a-service to its Internet customers.

With DevForce Silverlight, you get all of the above capabilities in a tool that permits you to develop Silverlight

applications that use the Entity Framework in the same way that WPF Windows clients do.

Secure Services The Entity Framework only supports a two tier architecture in which the client talks directly to the

database. There are not intrinsic capabilities for authenticating users, authorizing access, or encrypting

data. This architecture relies entirely on coarse grained network and database measures to secure the

application and requires extra care to protect the client machine from theft or intrusion.

This level of security is not good enough in many environments. There may be tough corporate or legal

mandates to protect sensitive data from unauthorized access. A client machine could fall into

mischievous hands. Any .NET program is easily disassembled. A determined malefactor could discover

the client-side application security measures, develop counter measures, and attempt unauthorized

persistence operations.

Connection Security

The trouble begins with the database connection string. In a two-tier world, each client must provide the

Entity Framework ObjectContext with a database connection string before it can access the database.

The database is easily compromised if the string contains a user and password. Encrypting the string

until the moment of use certainly helps – if you remember to do so – but still amounts to security-by-

obfuscation. It is much safer to rely on the operating system to authenticate the user to the database via

the Security Support Provider Interface (SSPI) as when the MS SQL Server connection string specifies

“Integrated Security=SSPI;”.

Moreover, each database connection is unique, defeating the performance advantage of connection pooling.

This technique works but there are problems. The IT management burden grows heavy when there are

many application users scattered across a widespread corporate network. New users must be added

both to network directories and to the database’s own list of authorized users. Departing employees

should be removed from all directories. The application administrator rarely maintains the network and

database logons so there are communications breakdowns that lead to mistakes.

Page 20: Dev Force 2010 Developers Guide

20 | P a g e

In a DevForce n-tier deployment, the Business Object Server (BOS) stands between the client and the

database.

The client must login to the BOS before the BOS makes any requests on the client’s behalf. After login

every transmission from client to server is accompanied by an encrypted session token that identifies

the client.

Domain Server / Active Directory authentication and impersonation are viable alternatives for LAN users

and can be combined with alternative login mechanisms when users access the application from outside the

corporate network.

Clients no longer access the database directly. They don’t hold a connection string nor issue vendor SQL

calls. They don’t know where the data physically reside.

Instead they ask the BOS to fetch and save data on their behalf and only commands and object data

travel over the wire. The BOS, running on a secure machine, connects to the database with its own

private connection string. The BOS performs all database operations.

Authorization

The Entity Framework has no authorization mechanisms. In most cases, the application relies upon

authorization settings in the database – settings which operate crudely at table levels and do not reflect

more detailed business rules. Application-specific authorizations can only be enforced in the client. The

ability to limit order approval or restrict access to a patient record depends entirely on business logic

executing in the client.

With the DevForce BOS in place every query and save operation is subject to inspection. The BOS

invariably calls certain customizable secured operation methods, passing along the client’s Principal so

each method can identify the client user and his assigned roles. The method can determine if the user is

allowed to perform the requested operation and what action to take if permission is denied.

Every step in this process, from login to security check can be tailored to meet the particular needs of

the application. There is nothing that client can do to thwart these measures. The BOS will execute them

like clockwork and the client has no access to the server, no ability to inject malicious code.

Encryption

The developer is free to engage the kind of encryption that is most appropriate. SSL is typical but other

methods can be inserted in the pipeline. DevForce prefers to use Microsoft Windows Communication

Foundation (WCF) for client-to-server communications; the WCF security-related configuration options

are all available.

Client Performance with DevForce Caching Fulfilling a request for data with a trip to the database is thousands of times slower than satisfying the

same request from local memory. The trip is longer still when the database resides across the network.

Page 21: Dev Force 2010 Developers Guide

21 | P a g e

That’s why responsive, data-intensive business applications cache entity data locally. If we’ve asked for

the data before, we should not have to ask for the same data again – at least not immediately.

Do applications ask the same question twice? Yes they do. Users are always cycling among several active

tasks; each time they return to a task underway, the application re-issues a query. The developer might

take pains to cache such queries herself - but that’s a lot of bookkeeping code write and maintain.

DevForce Caching and LINQ to DevForce

The DevForce Entity Manager maintains a query-able, client-side entity cache.

By “query-able” we mean that we can always apply a LINQ to DevForce query to the in-memory cache.

LINQ to DevForce is a LINQ implementation that enables queries to both the entity cache and to remote

data sources.

Let’s look at an example. We want to see the orders of star sales rep, “Nancy Davolio.” We compose a

LINQ query that searches for orders of the rep who’s first name = “Nancy” and whose last name =

“Davolio.” The first time we run it, the Entity Manager realizes that the query is new and sends the

query over the wire to the BOS. The results come back after a fraction of a second or several seconds,

depending upon the amount of data, the load on the database, and the speed of the network.

A minute later we ask for Nancy’s orders again. The Entity Manager recognizes the repeat query and

looks only in the local cache. It returns with the results immediately.

Behind the scenes the DevForce Entity Manager maintains both a cache of entities and a cache of

queries. The query cache is the memory of queries run against the database. When DevForce executes a

LINQ to DevForce query it checks this query cache first. If it finds the query it assumes the query can be

satisfied by the entity cache. It then translates the LINQ expression tree into search operations against

that cache.

The developer can inspect, add, remove, clear, and update the contents of both the entity and query

caches.

Responsiveness with Asynchronous Queries Responsiveness is subjective. The application is fast or slow if the user thinks it is. Users worry if the

application freezes for more than a second. A prolonged delay when the application launches or a

heavy screen loads is a common cause for complaint. Initialization queries or big data transfers are often

the source of the problem. You can alleviate the pain by fetching the data in background with

asynchronous queries.

The Entity Framework does not support asynchronous queries. DevForce does. It is easy to fire off a

series of async queries before displaying a form on screen. The form appears immediately and fills as the

data arrive.

Page 22: Dev Force 2010 Developers Guide

22 | P a g e

Some entities are more volatile than others. The list of provincial and city tax rates is probably constant

during a particular session. Inventories, on the other hand, are changing constantly and screen full of

quantities on hand should probably be refreshed every few minutes (or seconds perhaps). DevForce

async queries on a timer can keep that screen current without stalling the UI while the application polls

for changes.

There is always the danger of a runaway query – the query that pulls down so much data that it either

freezes the UI for agonizing minutes or times out. Fortunately, it’s easy to use the LINQ extension

method Take() to pull down sequential sections of a collection of entities. The following query, for

example, will bring down the first 100 customers, ordered by the name of their company:

C#

var query =_mgr.Customers.OrderBy(c => c.CompanyName).Take(100);

We can make the application appear extremely fast by combining a Take() query that requests a small

set of data with an asynchronous query that requests a larger set. Suppose, for example, that the user

requests several thousand orders. We don’t know for sure he’ll do so, but we’ve seen it before. So we

take defensive measures.

We first compose the user’s order query in the usual manner. We then suffix it with a call to Take() that

limits the request to a safe maximum of 3,000 orders. We submit this one as an asynchronous query

because we know from experience that it will take several uncomfortable seconds to return.

We follow immediately with the same query, also suffixed with a call to Take(), this time limited to 100

orders. This one we submit synchronously1; we’re willing to wait a half second for this one. It returns as

a list and we present the first 100 orders. The original request for 3,000 eventually arrives; the call-back

method fills the list. On screen, the order grid magically grows from 100 to 3,000. The user is delighted.

Note that there is also a Skip() extension method that can be used if you want something other than the

first n members of an ordered result set. The following query will bring down the next 100 customers:

C#

var query = _mgr.Customers.OrderBy(c => c.CompanyName).Skip(100).Take(100);

Model Design and Code Generation Architects are increasingly convinced that we should design business objects with a blind eye to the way

their inner state are stored. Our job is to interpret the user stories, to tease out the logic and data

1 In Silverlight applications all queries must be asynchronous, so in that case we will have to do both of our queries – the larger

one and the smaller one – asynchronously. In a Silverlight app, we might choose to tie up the user interface of our application

by other means (such as displaying a child window) while waiting for the smaller query to return.

Page 23: Dev Force 2010 Developers Guide

23 | P a g e

necessary to support those stories. A business object model gradually emerges and from that model we

later discover the storage scheme that fits best.

This approach is called Behavior Driven Development (BDD) because it encourages us to start from the

required application behavior and work toward the implementation rather than leap directly to data

design (as most of us old folks have done our entire careers). If a user story says “the order date must

precede the delivery date”, it is clear we’ll need two date fields. When the story says “the user enters an

order date” and “the user enters a delivery date”, we will know enough to give our Order class

properties to get and set these dates.

On the other hand, our Order class won’t have an “approval date”, a “credit checked date”, a “status

changed date” or any other date unless another user story calls for them. No peeking to see if these

fields are in the Order table!

We won’t worry just yet about how or where the order and delivery dates are stored. BDD says we

should wait to the “last responsible moment” before committing to a storage scheme. Meanwhile, we

can code and test our Order class now.

As storage blindness is rarely possible in real life, we should at least hang a curtain to hide the storage

details – and peer behind that curtain as little as possible.

The Entity Data Model helps by separating the conceptual data model from the storage schema. There is

no mistaking the fact that the conceptual data model remains a data model – well short of a business

object layer whose members combine behavior and state. Moreover it exists for one reason only: so

that we can move values between business objects and storage when that time inevitably arrives.

So it is actually a model of the state within the business objects rather than a model of the business

objects. Nonetheless, we should be able to maintain the pretense that our state is purely conceptual

and could be moved to any form of storage. We only commit to a storage scheme when we’re in a

different frame of mind.

This kind of design separation is extremely difficult to accomplish by hand. There is a lot of tedious

programming for each business object, most of it concerning access to the fields of persisted data. An

object mapped to a table row of twenty columns could yield a couple of pages of code. The slightest

change to the storage schema necessitates a revision of this code.

We won’t do it without adequate tools and code generation. We would simply lack the patience and

discipline.

Entity Framework Development

The Entity Framework designer presents a visual canvas upon which to draw entity classes, the

relationships among them, and the mapping to the storage schemas.

Page 24: Dev Force 2010 Developers Guide

24 | P a g e

The Entity Framework code generator produces a partial class file with properties to access persisted

data fields. It also inscribes navigation properties that return related entities; the Order.Customer

property returns the Customer object associated with a given Order instance.

Because a business object is more than data and needs more logic – more behavior – than just data

access properties, the generated class file needs a companion partial class file. The design tool can’t

generate the companion file – only the developer knows what belongs there. The developer creates this

file and pours her custom business object behavior into it. The compiler combines the two files, yielding

a complete class with both business logic and data management capabilities.

This two-part, “bicameral” file structure is effective in keeping developer and generated code in

separate rooms. In principle the generator can be run repeatedly – rearranging the generated code

“room” – without disturbing the furniture in the developer’s room.

The Entity Framework code generator falls short in several respects:

The designer does not give the developer adequate control over the generated code

The generated properties are not adequately extensible, limiting the developer‟s ability to abstract out the

business logic shared across business objects.

The code generator blocks introduction of “base” classes into the inheritance hierarchy, limiting the

developer‟s ability to inherit common business object behavior.

Rigid Code Generation

The Entity Framework code generator grants the programmer only limited control over the generated

class code. For example, it emits public properties for all mapped data values, even those you don’t

want exposed. And it always generates properties with both getters and setters. This is a reasonable

default but is not desirable in every case. The primary key value is usually immutable; its property

should be read only if it can be read at all.

Anemic Data Properties

The bicameral approach works fine when we can locate the business logic in the developer’s custom

partial class file. It’s easy to put calculations and workflow rules there when they concern the entire

object. For example, this is the place to augment the order object with an InvoiceTotal property that

sums the cost of all item details.

But a great deal of business logic is only effective when it executes inside the data access properties –

and these properties reside in the generated file. Suppose we want to constrain the transition from one

order status value to another; perhaps the status proceeds from “new” to “approved” to “shipped” to

“delivered”. We should reject any attempt to transition directly from “new” to “shipped”. Maybe we

should block unauthorized users from changing the status at all. The critical place to catch validation and

security violations is inside the OrderStatus property itself.

The Entity Framework did not generate the OrderStatus property with these capabilities. We cannot

add them to the generated property code ourselves; the designer will overwrite our change the next

time we use it – as we surely will in response to changing application requirements.

Page 25: Dev Force 2010 Developers Guide

25 | P a g e

The generated code must have adequate extension points – mechanisms that enable the developer to

inject behavior into the properties without touching the code itself.

Unfortunately, the Entity Framework generates anemic property accessors. Here is another example:

[EdmScalarPropertyAttribute()] public string SocialSecurityNumber { get { return _socialSecurityNumber; } set { OnSocialSecurityNumberChanging(value); ReportPropertyChanging("SocialSecurityNumber"); _socialSecurityNumber = value; ReportPropertyChanged("SocialSecurityNumber"); OnSocialSecurityNumberChanged(); } }

The “getter” is not extensible. It simply returns the social security number field value. What if the user is

not authorized to view that number? There is no way to block the attempt to read this value or to mask

it so the user sees only a safe portion of it (e.g., the last four digits).

The “setter” has a few extension points. There are reporting methods that could alert the application to

changes.

The ReportPropertyChanging and ReportPropertyChanged methods defer to an Entity Framework

ChangeTracker object that monitors current and original property values. It could be useful to a watching

application component (e.g., for data binding support).

There are the partial methods, OnSocialSecurityNumberChanging and

OnSocialSecurityNumberChanged, with which the developer can implement some limited logic specific

to Social Security Numbers. Observe that the incoming value cannot be transformed before it reaches

the field; we can complain (i.e., throw an exception) but we cannot heal.

We are out of luck if we need generalized property logic that works across multiple properties. We

shouldn’t have to manually implement an On…Changing or On…Changed method for every property we

want to validate. We should have a model-wide solution to validating changes that centralizes validation

rules and manages them as resources … as we do in DevForce. And remember: validation is but one

example of logic we could manage as metadata and introduce dynamically into the property.

Missing Inheritance

The Entity Framework supports inheritance hierarchies but only if each class in the hierarchy is mapped

to a physical database table. The only base class that isn’t mapped is the Entity Framework’s own Entity

class.

There is no room to insert a class into the hierarchy that provides pure behavior. This is a serious

omission. Years of real world application building confirm the wisdom and necessity of at least one base

class that provides behavior that all business objects have in common. This is the application model base

class, not Microsoft’s or IdeaBlade’s. Such a class could

Manage persistent auditing fields such as LastModifiedBy and LastModifiedDate.

Page 26: Dev Force 2010 Developers Guide

26 | P a g e

Generate separate audit trail objects during save.

Implement data binding interfaces such as IDataErrorInfo.

Cache broken validation rules.

Provide access to the application‟s Dependency Injection or Service Locator facilities.

It is not uncommon to introduce similar classes elsewhere in the hierarchy. We might want an

Inventory class in support of several distinct types of inventory, each mapped to its own table; we

shouldn’t have to have an Inventory table too.

DevForce Code Generation

The code generation in DevForce addresses each of these deficiencies.

The developer can specify an abstract base class that will never have a corresponding member in a data

repository and insert this class anywhere in the business object class hierarchy. It’s easy to set an

application base class from which all new business objects derive by default.

Like the Entity Framework, DevForce generates a partial class file covering the persistent data, leaving the

developer to write custom business logic in a companion file. But DevForce gives the developer better control over

the generated code. For example, using DevForce she can

Decide which properties to make public and which to hide

Make any property read only

Include or exclude DevForce value verification

Impose a required-value requirement on a property mapped to a nullable column

Property Interceptors DevForce provides a mechanism to intercept and either modify or extend the behavior of any .NET

property. This interception is intended to replace, and expand upon, the technique of marking

properties as virtual and overriding them in a subclass. This facility is a lightweight form of what is

termed “Aspect-Oriented Programming”.

Interception can be accomplished either statically, via attributes on developer-defined interception

methods, or dynamically, via runtime calls to the ‘current’ instance of a PropertyInterceptorManager.

Attribute interception is substantially easier to write and should be the default choice in most cases.

You can learn about property interceptors in the “Property Interceptors” section of the Learning

Resources.

Multiple Data Sources An Entity Data Model maps all of its entities to tables in a single database.

This is unrealistic for the many enterprise applications whose conceptual data models integrate

information from multiple resources. It’s not uncommon for an application to draw upon data resident

in three or four different databases.

Page 27: Dev Force 2010 Developers Guide

27 | P a g e

Consider, for example, a custom ERP application that keeps order information in one database and

accounting information in a separate database under the control of a third party accounting package.

Business requirements are such that a contract for a new order stimulates a cascade of credits and

debits. The ledger entries refer back to the order number and it must be possible to navigate from an

order to its entire ledger history. Both databases must be updated when saving the new order. It would

be a catastrophe if the order was added but not the ledger entries. We require a distributed transaction

which means that the changes to both databases must either all succeed or all fail.

This is an extremely difficult scenario for the Entity Framework. The two databases require two Entity

Data Models and two sets of entity classes. An Entity Framework ObjectContext can only manage

entities from a single model so we’ll need at least two ObjectContexts at runtime. Our scenario calls for

the ability to navigate from an order to ledgers and from a ledger entry back to an order. That will be

tricky because the related objects live in different ObjectContexts. Entity instances don’t know about

other ObjectContexts so an order won’t know which ObjectContext holds its companion ledger entries.

The developer will have to create some clever infrastructure to make this work.

Saving changes to orders and ledger entries is no picnic either. We have to save orders and ledger

entries separately. The developer will have to be aware of the issue, set up a distributed transaction,

and make sure that the Entity Framework properly enlists both save operations in that transaction.

By contrast, DevForce supports multiple data sources. The Order and LedgerEntry classes can reside in

a single model so there is no need for a separate interface assembly. DevForce will generate the

navigation properties to walk from order to ledger entry and back again. And DevForce takes care of

setting up the distributed transaction and enlisting the save operations within that transaction.

Our example looks a bit like this:

We see that DevForce is relying on the Entity Framework for object mapping and persistence operations

while shielding the developer from unpleasant implementation complexities.

The critical factor is the introduction of the DevForce business object model as a construct separate

from the Entity Framework’s own conceptual data model. In effect, DevForce provides a higher level

abstraction over the Entity Framework object mapping abstraction.

Page 28: Dev Force 2010 Developers Guide

28 | P a g e

Lost Connections and Offline Applications The Entity Framework’s ObjectContext must always be able to connect to the database. If the

application cannot connect, any query will throw an exception. The ObjectContext cache is unstable and

unusable while the connection is broken so it is dangerous to continue even if something appears to

work.

Many applications operate in environments with unreliable connectivity. Mobile applications and

wireless laptops are vulnerable to sudden outages. Without DevForce, the developer must work hard to

protect against connectivity failures. The user’s pending changes could all be lost.

A DevForce application can be immune to these problems. The application can recover from an outage

and continue to process queries against the cache alone until the connection is restored. The cache

preserves unsaved changes, including newly added objects, so the user can continue working, albeit

constrained to the world of entities presently in cache.

A DevForce application can encrypt and save the entity cache to a local file with just a few commands.

Later, with a few more commands, the application restores the cache from that file. A bullet-proof

application might automatically store a user’s pending changes locally every few minutes “just in case.”

If the application crashes or the battery dies, the user could re-launch later and recover her work.

We use this same mechanism to develop applications that operate offline intentionally. The user pre-

loads the cache, preserves the cache locally, shuts down, re-launches while disconnected, does work,

saves that work locally, and finally saves the pending changes to the database when reconnected.

Someone may have saved changes to the same data while this user was offline. It happens while online

too but the risk is greater when the time from change to save is prolonged. The response is essentially

the same: DevForce detects the concurrency violation and the application resolves it, perhaps with the

user’s help.

Page 29: Dev Force 2010 Developers Guide

29 | P a g e

More DevForce Advantages We’ve seen the DevForce capabilities that are most critical for enterprise application development.

There are other ways in which DevForce improves upon the Entity Framework. They may not be as

critical in the majority of applications but they can significantly enhance developer productivity and

code quality and are worthy of comment here.

Entity LifeCycle Events The business object and upper application layers often need to know what the persistence layer is

doing. The Entity Framework functions silently most of the time. It raises a SavingChanges event but

won’t tell you when the save operation succeeds or what entities were saved. There is no easy way of

knowing when it reaches out to the database or returns with data.

DevForce provides pre- and post- events or interception points for all significant moment in the “life-

cycle” of an entity. Client side events include Creating and Created, Fetching and Fetched, Saving and

Saved, Deleting and Deleted, Removing and Removed.

There are also life-cycle extension points on the server-side (BOS) . These include the ServerSaving and

ServerSaved methods so that developers can add custom processing immediately before and

immediately after the save transaction. The ServerSaving method has access to the entities to be

saved; the method can manipulate these entities, add to them, and remove them, before turning the

final list over to DevForce for the save operation. The ServerSaved method knows if the transaction

succeeded or failed and can invoke another server-side process as appropriate. Such a process might

send a message to another service running in the hosted environment.

Lazy Load by Default (The material in this section applies to DevForce WinClient but not to DevForce Silverlight, where all data

retrieval is asynchronous.)

DevForce navigation properties return a result if possible. The expression Order.Customer returns the

order’s customer if it has one. If the customer is already in cache, DevForce returns it. If the customer is

not in cache, DevForce fetches it from storage.

The behavior is the same if the navigation property returns a collection. The expression

Order.OrderDetails returns the order’s line items, retrieving them from storage if they are not found

in cache.

The Entity Framework takes a contrary approach. The navigation property it generates for

Order.OrderDetails returns an empty list if the line items are not already in cache.

Object-oriented guidelines say a property should return quickly. A database query is not a fast

operation. Therefore the team reasoned it should return nothing rather than return what the caller

clearly expected: the list of line items.

Page 30: Dev Force 2010 Developers Guide

30 | P a g e

We agree with the rule in general. But we can think of no use case in which returning an empty list from

Order.OrderDetails is the right thing to do. It only punishes the caller who will now have to write

several lines of defensive code to satisfy the guardians of object-oriented propriety.

IdeaBlade decided to break the rule and provide useful behavior.

The Null Entity Pattern DevForce scalar navigation properties always returns a business object. The expression Order.Customer

always returns a customer object. Of course the returned customer is the order’s real customer entity if

the order actually has a customer. If the order doesn’t have a customer, DevForce returns a placeholder

object called the Null Entity.

The same navigation property if generated by the Entity Framework would have returned null.

Null values greatly complicate the developer’s life. She has to be on constant alert for null reference

exceptions. Data binding to a property that can return null is pure hell. A null reference exception

thrown during data binding results in an ugly red bullet on screen and an error message that baffles the

poor user.

A customer null entity has all the properties of a real customer. The programmer can distinguish a null

entity from the real thing when she has to but she doesn’t have to litter the code with null value tests.

Data binding survives nicely; a UI widget bound to a null entity displays a conveniently vacant value of

the developer’s choosing.

The null entity pattern spares developers many hours of pain both in writing and reading code.

Proper Merge Strategies When the Entity Framework fetches data from the database it must decide how to merge those data

into its cache. What happens if the retrieved entities match entities already in the cache? What if some

of those entities have pending unsaved changes or are scheduled for deletion?

By default the Entity Framework only adds unmatched entities. That leaves modified entities untouched.

But it also means that stale data are not refreshed. Inventory levels won’t be updated. The user won’t

know about depletions or replenishments unless she is “lucky” enough to try saving a change to one of

the adjusted products; the save will fail with a concurrency exception and she’ll know to refresh the

inventory level.

An Entity Framework query with the “overwrite” option with refresh the unmodified inventory level –

and wipe out the user’s pending changes to other inventory objects.

An Entity Framework query with the “preserve changes” option seems to do the right thing. It updates

the unmodified inventory level and preserves the user’s changes. Unfortunately, it obscures the fact

that the changed inventory item is out of sync with the database. Suppose there was one item left in

stock when the user fetched the inventory level. The user allocates it to her customer. Meanwhile, a

Page 31: Dev Force 2010 Developers Guide

31 | P a g e

different user sold the item to his customer, reducing the stock level to zero. After this user refreshes

her cache with “preserve changes” she still believes there is one item in stock. There is not indication

otherwise. She saves, intending to sell the item to her customer. The save succeeds and now the same

item has been silently sold to two different customers?

The DevForce offers equivalents to the Entity Framework merge strategies; they have their place. But

the DevForce “preserve changes” option also preserves the pending concurrency conflict. The other user

sold the item first and DevForce will prevent her from selling it twice.

The DevForce Verification Engine DevForce provides a robust “Verification Engine” for validating the correctness of business objects. The

developer can code custom verification rules and apply rules to objects by decorating properties with

attributes, specifying the rules programmatically in the business object, or by reading them from

metadata and adding them to the engine at runtime.

While the application could suspend business object validation until just before save, user’s prefer to be

alerted immediately when they enter invalid data. Validation should be performed in the business

object rather than the UI. Business object properties should validate proposed values as those values

are conveyed from the UI to the object. DevForce supports this approach by inscribing calls to the

Verification Engine inside the property setters.

Entity Metadata The developer sometimes needs to know aspects of the conceptual data model itself. For example, she

might need to iterate over all the child relationships of an order without knowing what those

relationships are in advance.

The metadata about such features of the model are often hard or impossible to find in the Entity

Framework. DevForce records these features in metadata objects that can be easily reached

programmatically through the EntityMetadataStore class. See the “Object Persistence” section of the

Learning Resources for detail.

Eager Entity Loading By default, a query only returns the entities we ask for. If we query for orders, we get orders – and not

the other objects related to those orders such as the customers, shipping addresses, line item details,

and the product catalog. That’s usually a good thing. Why suffer the performance cost of fetching

related objects if we won’t need them? With “lazy load” we can get a related object as we need it, when

we need it, if we need it.

In many scenarios we know we need the related objects immediately. Suppose our application presents

the user with a list of orders. There is a grid beneath the list that displays the order details associated

with the currently selected order. Clearly we need both the orders and their details at the same time.

But if we stick with “lazy loading”, we’ll see a flurry of tiny database requests as the grid calls

Order.OrderDetails for each order in every displayed row. Performance will stink.

Page 32: Dev Force 2010 Developers Guide

32 | P a g e

Fortunately, in DevForce we can “eagerly load” the related objects by adding one or more “spans” to the

query. When we add a span that specifies the relationship between Order and OrderDetail, the query

engine fetches and caches the order details at the same time that it fetches and returns the selected

orders. The grid’s subsequent calls to Order.OrderDetails are satisfied quickly from the entity cache;

there will be no extra trip to the server.

The Entity Framework’s LINQ to Entities syntax has a comparable feature called an “include”. We can

add one or more “include” statements to eagerly load related entities. Unfortunately, there is no way to

manage the includes of a LINQ to Entities query; there is no way to discover if it contains an include, no

way to remove an include if it is not wanted. Moreover, an include instruction is a string, which means it

cannot be type checked.

In contrast, the DevForce programmer can inspect a LINQ to DevForce query for spans and add or

remove them at will.

Dynamic Data Source Configuration Data source connection management is unexpected chore. It seems simple at first: record the

connection in configuration file and get out of the way. But, for many applications, the connections

proliferate and the rules about who gets which connection become complex.

The Entity Framework isn’t much help in this department in part because it does not contemplate a

world of multiple databases. But DevForce can help you tame the complexity.

Two common scenarios illustrate the problem.

In typical Enterprise development cycles, an application advances through a sequence of “environments” that

begin with “Dev” and proceed through “QA”, “Stage”, and “Production”. The executables are the same but

the data source connection information changes at each step. It should be easy “flip a switch” and re-point

the application to the database (or set of data sources) that are appropriate for the targeted environment.

In some On-Demand applications, each tenant has its own database or data source set. Financial institution „A‟

has its database, „B‟ has theirs, and so on. Users launch a common application front end. When they enter

their credentials, the login module identifies the user‟s company and determines the corporate database that

is correct for that user‟s session.

In both illustrations, the data source schemas are the same across all session; what changes from

session to session is that actual database used.

The data model mapping schema associates each entity type with a home storage schema. That schema

has a symbolic name, the DataSourceKey.

We know the storage schema at design time. We know the DataSourceKey at design time. But we don’t

won’t know the actual data source to access until runtime. That’s when we’ll use the DataSourceKey to

locate the appropriate connection string and hook up to a real data source.

By default, DevForce looks for the connection string in an XML configuration file, expecting to find a

dataSourceKey node identified by the DataSourceKey name. The connection string should be an

Page 33: Dev Force 2010 Developers Guide

33 | P a g e

element within that node. Continuing our first example, we might locate any one of four connection

strings depending upon the environment.

We don’t want four separate configuration files. So instead, DevForce lets us maintain multiple

connection strings for each DataSourceKey. It differentiates among them by means of a

DataSourceKeyExtension, an extra bit of string associated with the DataSourceKey name. Now we can

record as many connection strings as we need for any conceptual data source by creating distinct nodes

uniquely identified by the both key name and extension. Nodes that share the same key name refer to

the same conceptual data source; the extension tells us which concrete data source to use at runtime.

We control runtime behavior by telling the client-side Entity Manager which extension to use. If we’re

running in the “QA” environment, we’ll specify a “QA” extension. If the application entities map to

conceptual databases “Alpha” and “Beta”, the application will connect to the concrete databases

identified by “Alpha_QA” and “Beta_QA”. When we run in production we switch to the “Prod” extension

and the application now connects to databases identified by “Alpha_Prod” and “Beta_Prod”.

Notice that databases travel in sets. There is the “QA” set and the “Prod” set. We can use this same

technique to support multi-tenant applications that store customer data in separate databases – an

approach often mandated by financial clients. An “Acme” client session runs against the “Alpha_Acme”

and “Beta_Acme” databases. The “Baker” client runs against the “Alpha_Baker” and “Beta_Baker”

databases.

The DevForce configuration file may not be the best place to store the connection information. In our

second “On Demand” scenario, we could be adding new application tenants frequently. Rather than

update the configuration file every time, we write a DataSourceKeyResolver to calculate and locate

connection information based on key name and extension.

Custom Key Generation Every entity must have a unique Entity Key so that the framework (a) can distinguish one entity from

another and (b) recognize when two apparently distinct object instances actually represent the same

thing.

The Entity Key is the conceptual equivalent of a primary key in a database table row. Like a primary key,

it can be a single value (e.g., an integer Id) or a composite key (e.g. as when a line item’s key consists of

it parent Order and Product ids).

A newly created entity must have a unique key before it can be added to the cache; this is true whether

we add the entity to the Entity Framework ObjectContext or to the DevForce Entity Manager.

Sometimes we can create the key on the spot. It’s easy if the key is a Guid or some other globally unique

value that can be determined by the client alone. It’s not easy if we must construct the key based on

values acquired from a remote source. That’s the more usual case. The key could be mapped to an auto-

incrementing column in the object’s home table. It could be generated by incrementing a counter stored

in a separate database table (e.g., a NextId table).

Page 34: Dev Force 2010 Developers Guide

34 | P a g e

Identity Column Keys

The Entity Framework supports the attributing of a column described in the storage model (SSDL)

section of the Entity Data Model with the StoreGeneratedPattern enumeration. This lets the Entity

Framework know that the back-end data store will generate a value for a column upon insert (or upon

both insert and update) so that when an entity containing such a column is persisted the Entity

Framework knows, post-save, to read the new value from the back-end data store and update the entity

in the Entity Framework cache.

The Entity Framework supports three states for StoreGeneratedPattern: None (the default), Identity,

and Computed. Columns flagged with StoreGeneratedPattern=Identity are those updated only upon

insert. Columns flagged with StoreGeneratedPattern=Computed are updated upon both insert and

update.

DevForce supports the StoreGeneratedPattern=”Identity” setting, extending its capabilities to

encompass entities in the DevForce client-side cache. These entities need primary key values

immediately upon creation, though they may not be persisted until much later. DevForce gives such

entities a temporary primary key upon creation so they can be referenced client-side without any trip to

the data source. Upon saving, their value is updated in the client-side cache to the value generated on

the server. The foreign key values in other entities that reference the targeted entity are also updated

to reflect the new, server-generated primary key value of the target entity.

The Entity Framework can generate the new key for you if the key is a single valued integer key mapped

to a SQL Server identity column. The Entity Framework can’t set the object’s permanent key; that won’t

happen until the newly created object is saved and even then it will be the database, not the

application, that determines the key. So the Entity Framework assigns a temporary key and refers to

that key when it adds related entities to the new object’s graph.

For example, upon creating a new Order, the Entity Framework assigns it a temporary key (e.g., “-1”).

When we add a new OrderDetail to that Order, Entity Framework inserts “-1” into the hidden foreign

key field of the OrderDetail that links the detail to the parent order. When the application saves these

new entities, the Entity Framework acquires the permanent ids from SQL Server and updates the objects

accordingly. Continuing our example, the Entity Framework learns that the new Order’s primary key is

“123” and updates the order’s id.

It also takes a critical second step: it finds all associated OrderDetails and updates their “ParentOrderId”

column values from “-1” to “123”. “Id Fix-up” is our name for this propagation of permanent ids to

related objects. Only then does it try to save the fixed-up OrderDetails.

Coping with Custom Keys

Many applications are tethered to an existing database with it’s legacy primary key scheme. They can’t

use Guids. The key may be a simple integer acquired from a counter table named NextId. It might be a

semantic key that combines the counter value with “meaningful characters”; maybe the order key

includes the state and fiscal year as in “FY07-0270-CA”.

Page 35: Dev Force 2010 Developers Guide

35 | P a g e

We’ll have to write the logic ourselves. When we create the order, we read the current counter from the

NextId table, bump it for next time, calculate our key, set the Order’s key – and then we’re ready add

the entity to the ObjectContext. It’s a pain but it’s manageable for a continuously connected application.

It’s much harder if we must support an application that can operate offline. We won’t always be able to

reach the NextId table so we can’t always calculate the permanent keys immediately. We’ll need a

custom temporary key and Id Fix-up scheme.

DevForce can do all of this for you. You write a custom Id calculation class that conforms to a DevForce

interface. DevForce discovers the class and manages key creation, temporary keys, and Id Fix-up during

the save. Of course it works even when your application runs offline.

Declarative Concurrency Column Management Many applications must guard against the possibility that two different users will unknowingly edit and

save the same entity simultaneously. Without some kind of checking, the last person to save wins. If I

sell a particular item and you sell the same item, we will have sold the same item twice although the

database will show only that you sold it.

I could have put a database lock on the item record, thus preventing you from reading and editing it.

Such “pessimistic locking” harms performance and leads to troubling lock-out scenarios. Neither

DevForce nor the Entity Framework supports such a physical locking scheme.

The Entity Framework relies on “optimistic concurrency” techniques to detect and resolve concurrent

access conflicts. Optimistic concurrency assumes that two users rarely wrestle over the same record and

therefore allows all users to access records freely. If two users, such as you and I, try to update the same

record, it detects the conflict and terminates the second save; it informs the second client be raising a

concurrency exception.

The Entity Framework implements optimistic concurrency by comparing the value of a concurrency

column in the pending record with the value of that column in the stored record. If the values are the

same, the pending record can be saved. If the values are different, the pending record is out of sync with

the stored record; the framework assumes a concurrency conflict and throws the exception.

This technique works so long as the concurrency value is changed after each successful save. Who is

responsible for that change? The Entity Framework says that you are.

You are fortunate if the database table has an update trigger that can do it. Otherwise, you have to

write the code that updates the concurrency column and you have to remember to call it at the right

moment.

DevForce can handle the concurrency column update for you. In DevForce, you declare the concurrency

column (or columns) and pick a method from a list of concurrency column update methods. DevForce

will call that method at the appropriate time. Yes, you can extend the list with a custom method.

Page 36: Dev Force 2010 Developers Guide

36 | P a g e

Undo The Entity Framework lets you accept all entities with pending changes (thus disguising a discrepancy

between data in session and data in storage!) but won’t let you roll back changes – either individually or

collectively – without many lines of programming. “Undo” is a one line command in DevForce.

Furthermore, with DevForce, you can move a set of entities into another Entity Manager “sandbox”,

which allows you to isolate a set of changes or even an entire workflow from disrupting the state of the

rest of the system.

Sandbox Editors Imagine that customer “Jim” calls to adjust one of his orders. You find the order in the list and open it in

an editor and begin working on it. You’re in the midst of changing deliver addresses, order items, billing

information, etc.

Suddenly, premium customer “Sally” calls you with an urgent request for a new order that you must

enter right now. Jim kindly agrees to complete his changes later. You begin Sally’s order in a second

order editor.

You are half way through Sally’s order when Jim calls you back. He says “never mind, that order we were

changing is just fine the way it was.” You switch briefly over to Jim’s order and discard all changes simply

by shutting down the order editor. You return to Sally’s order editor, complete it, and save.

There are two distinct orders in flight in this example. Each has its own set of entities some of which may

overlap (e.g., the list of shippers) although most do not. With DevForce, you can create separate Entity

Managers – with separate caches – and maintain these editor sets separately, each in their own

“sandbox”. The entities in the “Jim” Entity Manager are isolated from the entities in the “Sally” Entity

Manager and all of these entities are isolated from the list of orders held in the application’s main Entity

Manager.

Now imagine that this scenario takes place off line. There is no access to the database. That still works in

DevForce because you can easily pass copies of entities from one manager to the next without going to

the database. You might even prefer this approach when connected if the performance of your

application is at a premium and bandwidth is poor.

Managed Lists Keeping lists of entities up-to-date is a recurring application problem. It’s the holiday season as I write

this so let’s imagine we’ve written Santa’s inventory tracker. The tracker displays a list of undelivered

packages on Santa’s dashboard. As each package finds its intended child, the list should grow shorter.

An elf in the back of the sleigh is updating package information on a separate screen, marking each one

“delivered” as it drops down the chimney. Santa sees the same dashboard on the console monitor

because he and the elf are cabled together.

Page 37: Dev Force 2010 Developers Guide

37 | P a g e

What makes the list shrink when the elf marks the package delivered? Traditionally, we’d have written

the logic ourselves. But there is a problem: the elf’s module doesn’t know about the list displayed on the

dashboard. So it’s not as easy as remembering to remove an item from UndeliveredList when the elf

clicks the “Delivered” button. We’ll probably need some kind of cross module event scheme.

DevForce can handle this for us automatically with its managed list feature. Let the two modules share

the same Entity Manager, let the list be governed by this manager, give the list the appropriate

predicate – “keep item if not delivered”- and the list takes care of itself.

Conclusion

IdeaBlade has been in this arena since.NET 1.0. The DevForce product has long offered most of the

capabilities described in this paper including the multi-tier ORM, client-side caching, and code

generation.

The Microsoft Entity Framework is a solid contribution to the field and its very existence confirms the

widespread need for an infrastructure like DevForce. But the Entity Framework by itself cannot fulfill the

needs of many enterprise applications. The productivity isn’t quite there. The generated code lacks

essential support for business object development. Its two-tier architecture limits the application’s

ability to reach a distributed user community with the required performance and security.

With DevForce, developers can quickly realize the potential of an object-oriented, multi-tier, enterprise

application connecting hundreds or thousands of users.

Page 38: Dev Force 2010 Developers Guide

38 | P a g e

Hello, DevForce

Hello, DevForce ........................................................................................................................... 38

DevForce Application Architecture - The Big Picture ........................................................................................ 38

DevForce and the ADO.NET EntityModel ......................................................................................................... 40

Your First DevForce Application: a Walk-Through ............................................................................................ 41

Understanding the App.Configs .......................................................................................................................... 71

Monitoring Activity ............................................................................................................................................. 72

Appendix: Listings of Sample App.Config Files ................................................................................................. 74

Appendix: Probing Sequence for the App.Config File ........................................................................................ 75

Creation means

finding the new world

in that first

fierce step

with no thought of return.

David Whyte, “Statue of Buddha”

Don’t look back. All change, all creation, is attended first by grief for what is lost followed by the clarity

in moving on with no thought of return.

DevForce is not magic and you’re unlikely to build an enterprise application over night. But you can build

a good application that you’re proud of in reasonable time. Once you lay to rest your old habits and

have grieved for them awhile, the new path will embrace you and, in spare moments, you may wonder

how you ever did it that old way.

“But I’m so happy in my comfortable way. What if things go wrong? That DevForce thing is just a little

intimidating.”

This chapter should ease you across the threshold.

DevForce Application Architecture - The Big Picture A DevForce application relies upon a layered architecture for data access.

At one end is a data source – typically a relational database. At the other end is the user interface which

works with business objects in a business object model. There are several components in the middle.

Page 39: Dev Force 2010 Developers Guide

39 | P a g e

Figure 1. Application Components in a DevForce Application

One of them, called an EntityServer, moves data (and data requests) between the ADO.NET Entity

Framework and DevForce business objects. The EntityServer leaves the direct communication with the

back-end database to the ADO.NET Entity Framework.

The EntityServer has a copy of the application’s business object model so that it can instantiate

DevForce business objects server-side if need be. However, for most operations (such as simple data

retrievals), it forwards to the client-side EntityManager the data required for hydrating DevForce

business objects there, without ever instantiating DevForce business objects on the server. The data is

packaged and passed in a highly efficient format and process.

The ADO.NET Entity Data Model includes the mapping information necessary to translate between

locations in a relational data source and the corresponding persistent fields in the ADO.NET business

entities. The EntityServer mediates between the Entity Framework and the DevForce EntityManager

that manages the client-side cache used by your application.

The second important DevForce component is the EntityManager. The EntityManager takes

instruction from the higher levels of the application such as the UI, and forwards UI requests for entities

to the EntityServer. The EntityManager puts the received entities – obtained from whatever source

by the EntityServer -- into its entity cache and makes them available to the UI.

End users review the entities and make changes through the UI. The UI signals the EntityManager to

save the changes. It dutifully forwards the changed entities to the EntityServer which communicates

with the appropriate component to commit the data into persistent storage.

Page 40: Dev Force 2010 Developers Guide

40 | P a g e

DevForce and the ADO.NET EntityModel Visual Studio’s ADO.NET Entity Data Model wizard creates an EDMX file which contains descriptions of a

conceptual data schema (the object model), an actual data store schema (the database model), and the

mappings between the two.

Associated with the EDMX file is a code generator that renders the object model in code in a file named,

by default, <ModelName>.Designer.cs (or .vb). With DevForce installed, DevForce takes over the

generation of this code, and creates a file named <ModelName>.IB.Designer.cs (or .vb).

The developer’s first step in building the object model for her application will consist in creating an

entity model in an EDMX file. Typically she will use the Visual Studio Entity Data Model wizard to create

the initial version of the EDMX file. After that, she will work with some combination of the Visual Studio

Entity Model Designer and direct XML coding in the EDMX file, depending upon her preferences and

whether she needs to use features in her model that are not supported by the Entity Model designer.2

The DevForce Object Mapper adds its own elements and attributes to the .EDMX file, which coexist

happily alongside those contributed by the Entity Framework. In addition, DevForce supports the

aggregation of multiple EDMX files (and corresponding back-end databases) into a single “domain

model.” This domain model is manifested in the .NET code that DevForce generates to represent the

business model to your application.

You can extend the generated model using “developer partial class” files for each entity in the Domain

Model. You can create these yourself; or DevForce will generate starter files for you. These “developer

class” files are named “<EntityName>.cs (or .vb)” and are generated into the same Visual Studio project

that contains the Domain Model.

Those who have used DevForce 2009 may recall that in that version of DevForce two .NET versions of

the business model were employed: one that was DevForce-specific and one that was the standard

model generated by the Entity Framework. The Entity Framework version was only used server-side, and

behind the scenes; as a developer, you never interacted with it directly. Now, because of enhancements

to the Entity Framework, DevForce is now able to use a single model for all operations, server-side and

client-side. The resulting efficiencies boost performance and make new capabilities possible.

The object model generated by DevForce consists of business classes that inherit from

IdeaBlade.EntityModel.Entity. As previously mentioned, we refer to this version of the model as the

Domain model. The Domain model is “persistence ignorant”: it has no knowledge whatsoever of the

back-end datastore or the mapping between that and its objects. In an n-tier deployment, it is the only

model that is deployed client side. The client needs no connection information for back-end

datasources.

The Domain Model is a consumer of Entity Data Models, whose .edmx files define its content. Server-

side, DevForce delegates to the Entity Framework the jobs of communicating with the database(s) to

2 These are fewer than with the first version of the Entity Framework, but some remain.

Page 41: Dev Force 2010 Developers Guide

41 | P a g e

perform persistence operations including data retrieval and saving. The Entity Framework, in turn, uses

the compiled versions of the Entity Data Models, as well as connection information typically stored in an

app.config file, to do its work.

In DevForce, all direct communications with back-end data sources are considered, logically, as server-

side operations, which they will literally be in an application deployed across three or more physical

tiers. The application components that facilitate such communications, including the Entity Framework,

Entity Data Model, and DevForce EntityServer are considered server-side components, and are kept

logically separate from client-side components such as the DevForce EntityManager and the client

application. It is perfectly possible to deploy both the logical client-side components and the logical

server-side components to the client machine, and this is often the configuration used for much of the

development work even on enterprise applications. You’ll see how to do this in the walk-through

included later in this chapter.

When all application components including the database server are deployed on a single physical

machine, you have a “single-tier deployment”. When all application components except the database

server are deployed on a single physical machine, and the latter is deployed to a remote machine, you

have what is known as a “client-server” application. When client-side application components are

deployed on a separate machine from server-side application components, this is typically referred to as

“n-tier” deployment, even if the database server resides on the same machine as the application server

(e.g., the DevForce BOS).

However, since the strongest application security, widest availability, greatest scalability, and easiest

deployment are all associated with n-tier physical deployment, we figure it’s much to your benefit to

write your application from the beginning to permit that, and we make it as easy for you as we can.

Your First DevForce Application: a Walk-Through DevForce ships with four Visual Studio project templates, as shown below:

Page 42: Dev Force 2010 Developers Guide

42 | P a g e

Note that these templates are available only when targeting .NET Framework 4.

The purposes of these templates are as follows:

DevForce BOS Web Application. Creates a web application project for the DevForce Business Object

Server. This project contains more complete service configuration information than other DevForce

project types.

DevForce n-Tier WPF Application. Creates an n-tier WPF application with a DevForce BOS.

DevForce Silverlight Application. Creates a DevForce Silverlight application and BOS.

DevForce WPF Application. Creates a two-tier DevForce WPF application. A two-tier application does

not use a BOS, but you can easily add one later when wanted.

In this document we’ll walk through the process of creating a DevForce application, to give you a feel for

what it’s like. We’re going to use the DevForce n-Tier WPF Application for the walk-through. If you would

like to see a similar walk-through for a DevForce Silverlight application, please find our Four Simple Steps

paper and code solution, available from our Getting Started page (see the IdeaBlade DevForce entries on

the Windows Start Menu).

Page 43: Dev Force 2010 Developers Guide

43 | P a g e

Create Your Solution Using a DevForce Project Template

In Visual Studio, choose File / New Project from the main menu; under Visual Basic or C#, find the

DevForce 2010 templates; select the DevForce n-Tier WPF Application template; name and locate the

new solution (all as shown in the previous screen shot), and click OK. You should see something similar

to the following:

The template created a WPF project named “WpfApplication1”, whose resulting assembly is destined for

deployment on the client computer. Since we selected the n-Tier WPF application, the template also

created a web project (“WpfApplication1Web”) that will ultimately be deployed on a web server and will

host the DevForce Business Object Server. Note, however, that it will be perfectly possible to run, and

develop, this application on a single machine: to do so only requires changing the setting of a single

property in the app.config file. We’ll do that later.

Building the Model

Our next job will be to build the business object, or “domain”, model. In a DevForce app, both the

server-side and client-side applications need access to the domain model. We can accomplish that in a

couple of different ways:

1. We can create the model in the web project, and then add “shared file” links in the WPF project; or

2. We can create a separate project for the model, then add references to that project in the web and Wpf

Application projects (which already exist now in our solution).

Page 44: Dev Force 2010 Developers Guide

44 | P a g e

Both options are perfectly fine, but we have to choose one, so we’re going to create a separate project

for the model.

Create a new Windows class library project in the solution. Name it “DomainModel”.

Delete the “Class1.cs” file that Visual Studio creates; we won’t need it.

To the DomainModel project, add a new item, an ADO.NET Entity Data Model. Name the file

“NorthwindIBModel”.

Page 45: Dev Force 2010 Developers Guide

45 | P a g e

Clicking <Add> will launch a wizard to help you build your Entity Data Model. On the first dialog, accept

the default choice of “Generate from database”:

On the “Choose Your Data Connection” dialog, select or create a connection to the NorthwindIB

database, and tell Visual Studio to save the connections as “NorthwindIBEntityManager”:

Page 46: Dev Force 2010 Developers Guide

46 | P a g e

The EDM Wizard retrieves schema information from the database and presents you with a tree control

of choices for items on which to base entities in your model. Expand the Tables node and select the

following tables:

Customer

Employee

Order

OrderDetail

Product

Supplier

Accept the default settings for the two checkboxes:

Also accept the default model namespace of “NorthwindIBModel” (the name we specified earlier for the

model itself).

Page 47: Dev Force 2010 Developers Guide

47 | P a g e

If you receive the following security warning message…

… click <OK> so that DevForce can use the Entity Data Model to generate a C# or VB domain model.

Until and if you turn this warning off, you will see it whenever DevForce wants to regenerate your

domain model code.

The EDM designer will create an XML file with extension .edmx to capture the information you (and the

database) supplied using the wizard. Then it will render the newly created model visually:

Page 48: Dev Force 2010 Developers Guide

48 | P a g e

If you open the Properties panel you will see, in addition to standard Entity Framework properties, many

DevForce-specific ones:

Page 49: Dev Force 2010 Developers Guide

49 | P a g e

If you click in white space in the designer window, you see the properties that apply to the

ConceptualEntityModel:

Property Description

DataSource Key A name by which a particular data source

is identified within your code by

DevForce. With DevForce, you can link

your domain model to multiple different

databases.

Page 50: Dev Force 2010 Developers Guide

50 | P a g e

EntityManager Name The name by which you will refer to your

specialized IdeaBlade.EntityModel.Entity

manager in your code.

Generate Binding Attributes Specifies whether to generate the binding

attributes (including Bindable, Editable,

and Display) for entity properties.

Generate Developer Classes Specifies whether to generate partial class

files that permit you to extend your

domain model.

Generate Validation Attributes Specifies whether to generate .NET

validation attributes on entity properties.

Generate Verification Attributes Specifies whether to generate DevForce

verification attributes on entity properties.

Injected base type Name of a class from which you want all

of your entities to inherit.

Max.Classes Per File Limits the number of entities for which

code will be generated in a single file (in

order to prevent problems that Visual

Studio has with very large code files).

DevForce will automatically generate as

much files as are needed to fully define

your domain model.

If you click on a single entity, such as Customer, you see a difference set of properties, including

DevForce-specific properties:

Page 51: Dev Force 2010 Developers Guide

51 | P a g e

These properties allow you to specify authorization requirements for querying and saving the entity.

This is discussed in more detail in a security-oriented Learning Resources example.

Click on a specific property such as the primary key CustomerID…

Page 52: Dev Force 2010 Developers Guide

52 | P a g e

…or on a navigation property, such as Customer.Orders…

Page 53: Dev Force 2010 Developers Guide

53 | P a g e

… to see the Entity Framework and DevForce properties specific to that item.

As you can see, DevForce is completely integrated with the Entity Model Designer. It also takes over all

code generation for the .NET object model. If you select the NorthwindIBModel.edmx file in the Solution

Explorer, then right-click it and select Properties, you can see that DevForce has modified the default

Custom Tool name so that the default code generator won’t be used:

Page 54: Dev Force 2010 Developers Guide

54 | P a g e

If at any time you wish to restore use of the default (Entity Framework) code generator, you can simply

delete the “<RemoveToRestore>” prefix on this entry to do so.

Now let’s have a look at the generated code.

Three files now accompany the .edmx:

NorthwindIBModel.edmx.tt The template used in the generation of the domain model to

NorthwindIBModel.IB.Designer.cs.

NorthwindIBModel.edmx.ReadMe describes how to customize the DevForce code generation

templates.

Page 55: Dev Force 2010 Developers Guide

55 | P a g e

NorthwindIBModel.IB.Designer.cs contains the.NET code generated by DevForce:

Note how, at the very top of this file, you are instructed not to modify this code. DevForce owns it, and

will regenerate it at will to keep up with changes you make to the Entity Data Model. You’ll do your

customization in “developer partial class” files; we’ll discuss those shortly. But for now, have a look at

some of the items in the generated code:

Page 56: Dev Force 2010 Developers Guide

56 | P a g e

You see entity classes such as Customer, Employee, Order, and OrderDetail. Associated with each of

these entities is an EntityPropertyNames class, and a PropertyMetadata class. The EntityPropertyNames

class provides string constants for the names of each of the entity’s properties: you can use these

throughout your own code, wherever a property name is needed, in preference to hard-coding the

string value. The PropertyMetadata class contains static EntityProperty fields corresponding to each of

the entity’s business properties, which you can use in your code when an EntityProperty is required.

The main entity classes, such as Customer, inherit from IdeaBlade.EntityModel.Entity, and contain

definitions of all of the entity’s business properties. Many other facilities are available via the Entity base

class.

Before we leave our Entity Data Model, let’s fix a couple of property names. The Employee entity, like

the Employee table in the NorthwindIB database, has an association with itself, in order to reflect the

chain of management. This self-association yields two navigation properties, one to represent the

Employee upstream from the current one – i.e., his Manager – another to represent those downstream

– his DirectReports. The EDM designer knew from the discovered relationship that two such properties

Page 57: Dev Force 2010 Developers Guide

57 | P a g e

were needed, but it didn’t know enough about what they actually represent to name them very well, so

it just called them Employee1 and Employee2.

By selecting the Employee1 property and viewing its properties, we see that its Return Type is a

Collection of Employee:

Employee1 must therefore represent the downstream Employees; we’ll rename it to “DirectReports”.

Double-checking the Employee2 properties verifies what we expect – that it returns an Instance of

Employee – so we know that one represents the Manager and rename it accordingly.

Page 58: Dev Force 2010 Developers Guide

58 | P a g e

We also happen to know that the Employee navigation

property on the Order entity, which was automatically

added to the model upon discovery of the many-to-one

relationship in NorthwindIB between Orders and

Employees, represents an employee who acts in the role

of a sales representative for that Order. We‟ll rename it

to “SalesRep”.

Add References in the Web and UI Projects to the New DomainModel Project

There’s a great deal more we could, and eventually will, do to refine our model, but it’s already

sufficient fleshed out to support data retrieval and persistence, so let’s see how we can use it to do that.

1. Build the DomainModel project.

2. Add references to the DomainModel assembly in the WpfApplication1 and WpfApplication1Web projects.

You may note that both of those projects already have references to several DevForce assemblies. These

were put there by the project template that you used to create the solution initially.

Using the Model

The DevForce project template created a WPF Application project, so let’s add some code to that. First

we’ll flesh out – ever so slightly -- the MainWindow window that the template created, by adding a

TextBlock within a ScrollViewer inside the window’s layout grid.

The XAML we’re starting with is this:

XAML

<Window x:Class="WpfApplication1.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="MainWindow" Height="350" Width="525">

<Grid>

Page 59: Dev Force 2010 Developers Guide

59 | P a g e

</Grid>

</Window>

We’ll enhance that to this:

XAML

<Window x:Class="WpfApplication1.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="MainWindow" Height="350" Width="525">

<Grid>

<ScrollViewer>

<TextBlock Name="_outputTextBlock" TextWrapping="Wrap"

FontFamily="Courier New" />

</ScrollViewer>

</Grid>

</Window>

For the purpose of this very simple application, we’re going to use that TextBlock control like the output

window of a Console application, and simply write output strings to it. (We can get more sophisticated

later, with streaming video, cascading 3D animations, chirping monkeys, and touch-screen facilities!)

We could, of course, put code to retrieve and display data in the “code behind” area of the Window

itself, but let’s just put ourselves off to a good start and build a very basic Model-View-ViewModel

architecture so we can (a) keep our view extremely lightweight and (b) minimize the obstacles to our

application’s testability.

We’ll create a class we’ll call MainWindowViewModel, give it a public string property named Output, and

just bind our TextBlock to the value of that property:

XAML

<Grid >

<ScrollViewer>

<TextBlock Name="_outputTextBlock" TextWrapping="Wrap"

FontFamily="Courier New" Text="{Binding Output}" />

</ScrollViewer>

</Grid>

The MainWindow’s code behind starts out like this:

Page 60: Dev Force 2010 Developers Guide

60 | P a g e

C#

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

namespace WpfApplication1 {

/// <summary>

/// Interaction logic for MainWindow.xaml

/// </summary>

public partial class MainWindow : Window {

public MainWindow() {

InitializeComponent();

}

}

}

We’ll add a handler for the window’s Loaded event to do all of the following:

Instantiate the view model;

Set the view‟s DataContext to that view model; and

Call a Start() menu on the view model to initiate data retrieval and display.

C#

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

namespace WpfApplication1 {

/// <summary>

/// Interaction logic for MainWindow.xaml

/// </summary>

public partial class MainWindow : Window {

public MainWindow() {

InitializeComponent();

this.Loaded += new RoutedEventHandler(MainWindow_Loaded);

}

void MainWindow_Loaded(object sender, RoutedEventArgs e) {

MainWindowViewModel aMainWindowViewModel = new MainWindowViewModel();

this.DataContext = aMainWindowViewModel;

aMainWindowViewModel.Start();

}

}

}

Note that we did not have to know that the view’s Loaded event handler had to be a

RoutedEventHandler, etc.; we simply typed in “this.Loaded +=”, and then pressed the TAB key a couple

Page 61: Dev Force 2010 Developers Guide

61 | P a g e

of times to let Visual Studio set up an appropriate stub. We then filled in the code representing the

actions we wanted to take place.

Now let’s create that MainWindowViewModel class. Add a new class by that name to the

WpfApplication1 project and flesh out its code to look like this:

C#

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using SysComp = System.ComponentModel;

using DomainModel;

using IdeaBlade.EntityModel;

namespace WpfApplication1 {

class MainWindowViewModel : SysComp.INotifyPropertyChanged {

#region Constructor

public MainWindowViewModel() {

}

#endregion Constructor

#region Methods

public void Start() {

FirstSample();

}

public void FirstSample() {

StringBuilder aStringBuilder = new StringBuilder("Started FirstSample()...\n");

var customersQuery =

from cust in _mgr.Customers

where cust.ContactTitle == "Sales Representative"

orderby cust.CompanyName

select cust;

aStringBuilder.Append(string.Format("Retrieved {0} customers\n",

customersQuery.ToList().Count));

foreach (Customer aCustomer in customersQuery) {

aStringBuilder.Append(string.Format("Customer: {0}\n", aCustomer.CompanyName));

}

Output = aStringBuilder.ToString();

}

#endregion Methods

#region Data Bound Properties

public string Output {

get {

return _output;

}

set {

_output = value;

RaisePropertyChanged("Output");

}

}

#endregion Data Bound Properties

#region INotifyPropertyChanged

Page 62: Dev Force 2010 Developers Guide

62 | P a g e

public event SysComp.PropertyChangedEventHandler PropertyChanged = delegate { };

protected void RaisePropertyChanged(string propertyName) {

PropertyChanged(this, new SysComp.PropertyChangedEventArgs(propertyName));

}

#endregion

#region Private Fields

private NorthwindIBEntityManager _mgr = new NorthwindIBEntityManager();

string _output = "";

#endregion Private Fields

}

}

Note a few things about our ViewModel:

1. We made the class implement the INotifyPropertyChanged from the .NET namespace System.Component

model. That interface requires the definition of an event name “PropertyChanged”, to be raised by an

instance of this (MainWindowViewModel) class whenever the value of one of its public properties

changes.

2. We‟ve defined exactly one such public property – a string property named “Output” – which when set will

cause the PropertyChanged event to be fired. Most WPF and Silverlight user interface controls listen for

this event and respond to it by refreshing themselves with current data from the source object to which they

are bound. In our app, the TextBlock on MainWindow will get refreshed automatically whenever the value

of Output is changed.

Beyond that, our view model, when the Start() method is run, simply submits a LINQ query requesting

Customers who meet some condition; then uses that query to retrieve, first a count of such Customers,

and then the Customer objects themselves. With each operation it writes text to the Output property.

Change the Client-Side App.Config

We’re almost ready to go, but we must make a couple of tweaks to the client-side App.config file to run

the application initially in a single tier (or in a client-server configuration if your database is located

elsewhere). Once we’ve tested in a single tier, we’ll switch to n-tier to show how simply that can be

accomplished. The client-side App.config for the single-tier or client-server app looks like this:

XML

<?xml version="1.0"?>

<configuration>

<configSections>

<section name="ideablade.configuration"

type="IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core"/>

</configSections>

<ideablade.configuration version="6.00"

xmlns="http://schemas.ideablade.com/2010/IdeaBladeConfig">

<logging logFile="DebugLog.xml"/>

<objectServer remoteBaseURL="http://localhost" serverPort="9009"

serviceName="EntityService.svc" >

<clientSettings isDistributed="true" />

</objectServer>

<!-- Additional configuration can be added to override defaults.

See the sample config files in the Learning Resources for more information.

-->

Page 63: Dev Force 2010 Developers Guide

63 | P a g e

</ideablade.configuration>

</configuration>

Change the line:

XML

<clientSettings isDistributed="true"

to read:

XML

<clientSettings isDistributed="false"

Then, at the same level as the <ideablade.configuration> section, add a <connectionStrings> section as

shown below. You can find the <connectionString> section you need in the copy of App.Config in the

DomainModel project, where it was written by the Entity Data Model designer when you built the

model. Simply copy it from there and paste it into the App.config for the client project

(WpfApplication1).

XML

<connectionStrings>

<add name="NorthwindIBEntityManager"

connectionString="metadata=res://*/NorthwindIBModel.csdl|res://*/NorthwindIBModel.ssdl|r

es://*/NorthwindIBModel.msl;provider=System.Data.SqlClient;provider connection

string=&quot;Data Source=.;Initial Catalog=NorthwindIB;Integrated

Security=True;MultipleActiveResultSets=True&quot;"

providerName="System.Data.EntityClient" />

</connectionStrings>

The completed App.config should look like this:

XML

<?xml version="1.0"?>

<configuration>

<configSections>

<section name="ideablade.configuration"

type="IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core"/>

</configSections>

<ideablade.configuration version="6.00"

xmlns="http://schemas.ideablade.com/2010/IdeaBladeConfig">

<logging logFile="DebugLog.xml"/>

<objectServer remoteBaseURL="http://localhost" serverPort="9009"

serviceName="EntityService.svc" >

<clientSettings isDistributed="false" />

</objectServer>

<!-- Additional configuration can be added to override defaults.

See the sample config files in the Learning Resources for more information.

-->

</ideablade.configuration>

<connectionStrings>

<add name="NorthwindIBEntityManager"

connectionString="metadata=res://*/NorthwindIBModel.csdl|res://*/NorthwindIBModel.ssdl|r

Page 64: Dev Force 2010 Developers Guide

64 | P a g e

es://*/NorthwindIBModel.msl;provider=System.Data.SqlClient;provider connection

string=&quot;Data Source=.;Initial Catalog=NorthwindIB;Integrated

Security=True;MultipleActiveResultSets=True&quot;"

providerName="System.Data.EntityClient" />

</connectionStrings>

</configuration>

Run the App

Now we’re ready to run our app. A few seconds after launch, you should see the following display:

Add Business Logic to the DomainModel

So far, so good, but now let’s see how we add custom logic to our business model. As we previously

pointed out, the code that currently comprises the business model – all stored in the file

NorthwindIBModel.IB.Designer.cs -- is owned entirely by DevForce and will be overwritten by DevForce

whenever you change the content of the Entity Data Model (.edmx) file. That makes it a very bad place

to put custom code! Where such code should be put is in partial class files that extend the existing

model.

You can code such files yourself from scratch, but you can also have the code generator write starter

versions for you. To do that:

1. Open the Entity Data Model in the designer

2. View the Properties panel in Visual Studio

3. Click on empty white space in the designer window to see the properties of the Conceptual Entity Model.

Page 65: Dev Force 2010 Developers Guide

65 | P a g e

4. In the DevForce Code Generation section, find the property Generate Developer Classes, and set its value

to true.

5. Save the updated model (and respond to the Security Warning about the text template, if it appears, by

clicking <OK>).

You will now see one new file in the

DomainModel project for each of the entities

that you included in your model, as at right.

Let‟s look at the contents of the Customer.cs

file:

C#

using System;

using System.Linq;

using IbEm = IdeaBlade.EntityModel;

using IdeaBlade.Core;

namespace DomainModel {

// The IdeaBlade DevForce Object Mapping Tool generates this class once

// and will not overwrite any changes you make. You can place your custom

// application-specific business logic in this file. Generated: 3/31/2010 6:17:32 PM

//

// This partial class is the companion to the class regenerated by the

// Object Mapping tool whenever the model changes.

public partial class Customer : IbEm.Entity {

}

}

Page 66: Dev Force 2010 Developers Guide

66 | P a g e

That’s pretty unimposing. But since it’s a partial class it can be enhanced to extend the generated model

almost without limit.

For example, let’s add a static Create() method so we can create and initialize new Customers in a

standard way throughout our application.

First we add a statement to make items in the DomainModel namespace available within the class:

C#

using DomainModel;

Then we add the Create() method:

C#

static Customer Create(string companyName) {

Customer newCustomer = new Customer();

newCustomer.CustomerID = System.Guid.NewGuid();

newCustomer.CompanyName = companyName;

newCustomer.EntityAspect.AddToManager();

return newCustomer;

}

Our Create() method requires a company name. It instantiates a new Customer, and because the

Customer type has a GUID primary key, the code assigns a primary key value. It then assigns the

companyName value passed to the method to the corresponding property on the new entity, and then –

very importantly – puts the new entity into the local cache and under an EntityManager’s control. It

then returns a reference to the new entity to the calling method.

You can add other custom methods, and custom properties, as needed and as desired. But suppose

what you want to do is to override the behavior of a generated property. You can’t override or shadow

the property because it’s defined in the same class you’re working in. (Remember, you’re in a partial

class that is extending the existing, generated partial class.)

So how do you accomplish what you need? The answer is to use a property interceptor. This is a bit of

code that you provide and decorate with attributes that describe to DevForce your intentions for it. It is

then called by DevForce at an appropriate point in a Get or Set process to alter the result of that

operation. For example, here’s an interceptor that converts the value of Customer.Company to its

uppercase equivalent during a Get operation:

C#

[AfterGet(EntityPropertyNames.CompanyName)]

public String UppercaseLastName(String companyName) {

if (!String.IsNullOrEmpty(companyName)) {

return companyName.ToUpper();

}

else {

return String.Empty;

}

}

Page 67: Dev Force 2010 Developers Guide

67 | P a g e

If this interceptor is defined within the Customer class, DevForce assumes it is meant to apply to

Customers; and because we have said so in the [AfterGet] attribute with which we decorated our

interceptor method, DevForce knows the interceptor is meant to be applied only to the CompanyName

property.

Run the application again and you’ll see the result of adding the AfterGet interceptor:

You can define interceptors to be as general as you need: for example, you can provide an interceptor

method that will apply to every property of Customer; or even to every property of every entity in your

model! You can define interceptors that jump into the middle of a Get before a value is obtained from

the entity instance (which you could use, for example, to prevent certain data from being made

available to unauthorized users); after the value is obtained from the business object but before it is

delivered to the requestor (as in the interceptor above); before a proposed new value is pushed into the

business object for which it is targetted; and after it is so pushed. You can alter the process of getting

and setting business object property values in any way you need to do it.

One final note on the developer classes: recall that we got DevForce to generate starter partial classes

for extending the entities in our business model by setting the Generate Developer Classes property on

the ConceptualEntityModel to true. Even though we haven’t set that value back to false, we’re in no

danger of losing the customizations we just made to Customer.cs. DevForce will never overwrite an

existing developer partial class.

Running the Application in an N-Tier Configuration

Now we’re ready for the last step, running the application in multiple tiers. We created this application

using the DevForce N-Tier WPF Application template, and we’ve got a web application project in our

Page 68: Dev Force 2010 Developers Guide

68 | P a g e

solution which we haven’t used yet. The web application project hosts the DevForce Business Object

Server, and is almost ready to go, with a few more changes to the config files.

Client-side App.Config. We toggled the isDistributed flag off earlier to show the application running in a

single tier. Let’s turn that flag back on now:

XML

<clientSettings isDistributed="true"

This flag, along with the objectServer settings indicating the URL, port and name of the BOS, will tell the

client application where the BOS is located. The BOS in the web application project here is by default

also listening at this address, so no additional wiring is required.

XML

<objectServer remoteBaseURL="http://localhost" serverPort="9009"

serviceName="EntityService.svc" >

<clientSettings isDistributed="true" />

</objectServer>

Server-side Web.Config. We need to ensure the connectionStrings section is available here in the

web.config, since data access will now be performed here on the BOS and not in the client application.

We previously copied the connectionStrings from the Domain Model app.config into the executable

project’s app.config; we can now copy (or move if you prefer) this information into the web.config. We

don’t need any other changes in the web.config, so it should look something like this when done:

XML

<?xml version="1.0"?>

<configuration>

<configSections>

<section name="ideablade.configuration"

type="IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core"/>

</configSections>

<connectionStrings>

<add name="NorthwindIBEntityManager"

connectionString="metadata=res://*/NorthwindIBModel.csdl|res://*/NorthwindIBModel.ssdl|r

es://*/NorthwindIBModel.msl;provider=System.Data.SqlClient;provider connection

string=&quot;Data Source=.;Initial Catalog=NorthwindIB;Integrated

Security=True;MultipleActiveResultSets=True&quot;"

providerName="System.Data.EntityClient" />

</connectionStrings>

<ideablade.configuration version="6.00"

xmlns="http://schemas.ideablade.com/2010/IdeaBladeConfig" >

<logging logFile="log\DebugLog.xml"/>

<!-- Additional configuration can be added to override defaults.

See the sample config files in the Learning Resources for more information.

-->

Page 69: Dev Force 2010 Developers Guide

69 | P a g e

</ideablade.configuration>

</configuration>

With these changes we’re ready to fire up the application in n-tier! One more thing before hitting F5,

double-check that the web project will start up automatically. The “Always Start When Debugging” flag

should be True, to ensure the ASP.NET Development Server starts when the client project does.

Now build and run, and you’ll see … the same application window you saw before. But there’s a

difference this time, the application is now running in multiple tiers – the WPF client application is now

communicating with the BOS, which is now responsible for communicating with the data tier. The BOS

here is running on the same machine (localhost), but you can now deploy the BOS to another machine

when you’re ready. Deployment is a complex topic, covered separately, but you’ve now created an

application which can run in either a single tier or n-tier at the flip of a switch!

More Capabilities DevForce Can Add to Your Application

That completes our quick tour of the basics. There are, naturally, far more sophisticated things you can

do in your application with DevForce’s help. Just as a few examples, you can:

Page 70: Dev Force 2010 Developers Guide

70 | P a g e

Access multiple separate databases from your Entity Data Model (impossible with the unaided Entity

Framework);

Easily design your app to run disconnected from the database, saving changes locally until a connection to

the database is available and desired;

Order complex operations to be performed entirely server-side, with delivery of as little or as much

resultant data to the client as desired;

Leverage DevForce‟s rich validation subsystem to ensure absolute integrity in the data saved to the

database;

Intercept data retrievals and saves from and to the database, altering or preventing the operations as desired

based upon the user‟s privileges and any other factors you deem relevant.;

Instantiate multiple EntityManagers in your application, each managing its own cache, in order to provide

“work units” of information in which local changes can be saved or reversed without impacting other data

being used locally;

Fully leverage the power of LINQ (Language Integrated Query) for data retrieval and manipulation to free

yourself from SQL forever; and, of course,

Much more!

You can get detail on these facilities and techniques in other DevForce Learning Resources, including

topic documents, code samples, and videos. Enjoy the journey!

Page 71: Dev Force 2010 Developers Guide

71 | P a g e

Understanding the App.Configs You will soon discover that your Entity

Framework / DevForce app includes

many app.config files. Each has its

necessary and particular role, which

we’ll explain below.

The sample Visual Studio solution at

right includes two app.config files, one

in each of the following locations:

1. In the project for the Domain

Model (#1);

2. In the executable project (#2)

The App.Config in the Domain Model

project (#1 in the picture) typically gets

there by being generated by the Visual

Studio Entity Data Model designer. It

contains a configuration section with a

connectionStrings element. For a

sample, see Listing 1 in the Appendix

“Listings of Sample App.Config Files” at

the end of this chapter.

The App.config in the executable

project (#2 in the picture) typically gets

there because you’ve used a DevForce

project template to create the project.

Alternately you can create a new

app.config, or edit an existing one,

using the DevForce Configuration

Editor.3

The app.config in the executable project (#2) contains, most importantly, an ideaBlade.configuration

section which contains settings allowing you to configure your DevForce application. This config may

also contain configuration information for other elements of your application not related to DevForce.

3 “Config Editor” under IdeaBlade DevForce 2010 / Tools on the Windows Start menu.

Page 72: Dev Force 2010 Developers Guide

72 | P a g e

At run time, DevForce requires connection information for the data sources used by your application. In

a two-tier application such as the sample shown, this connection information should be located in the

app.config of the executable project. (In an n-tier application, in which a DevForce Business Object

Server is used, the connection information is needed only on the server, typically a web.config file when

the BOS is hosted by IIS, or otherwise a .config for the server. N-tier applications are covered in later

chapters.)

In our two-tier sample, since connection string information is required here by DevForce at run time,

you need to ensure that you’ve copied the connectionStrings from the app.config in your Domain Model

project (project #1) into the app.config used at run time (project #2). DevForce will not copy this

information for you, and you will receive an error at run time if you try to query or save data and you

have not supplied connection information. Note that you do not need to use the same database for

design-time work as you do at run time – therefore the connection information may differ in the actual

database/server specified.

Note that the App.config in your Domain Model project is used by the Entity Data Model designer at

design time when working with the EDMX file. The connectionStrings here tell the designer where your

design-time database is located, and allow you to update your Domain Model when needed. The

App.config here, since located in a class library, is not used at run time.

When Visual Studio builds your projects it automatically copies and renames the App.config files based

on the assembly name. For example in the sample shown, a DomainModel.dll.config will be created for

the Domain Model project, and a WpfApplication1.exe.config for the executable project. Only the

*.exe.config is used by DevForce at run time.

Monitoring Activity What is actually happening as we run the applications? When is it asking for data? What does the SQL

look like?

SQL Profiler We can always monitor activity on the SQL Server using SQL Profiler. Here we assume SQL Server 2008.

Launch Microsoft SQL Server Management Studio.

Select “Tools ►SQL Server Profiler” from the menu.

Select “File ►New Trace ..” from the Profiler menu and connect to your database server

Click [Run] on the [Trace Properties] dialog.

Return to Visual Studio and re-run the application [F5]

Page 73: Dev Force 2010 Developers Guide

73 | P a g e

The trace window fills, showing us exactly how we’re hitting the database.

DevForce Tracing As your application runs DevForce generates trace messages containing information (and warnings)

about its actions. These messages will help you diagnose and debug problems should they occur. You

can also use the DebugFns.WriteLine and TraceFns.WriteLine methods to send your own debug and

trace messages to DevForce.

By default DevForce writes these messages to a trace log file4 named “DebugLog.xml”5 in the executable

directory.

Open Windows Explorer.

Navigate to the ..\bin\debug directory under the executable‟s directory.

Launch DebugLog.xml.

The log appears in a browser window.

Each row speaks of some event during the life of the last application run. You’ll see database access

events among other event occurring from the start of the application until it shuts down.

You can launch the DebugLog while the application is running and refresh the browser from time to time

to see how the log is progressing as you move through the application.

DevForce TraceViewer

4 You can turn it off or filter it, or choose a custom logger.

5 There are companion .css and .xslt files in that directory as well so that the log displays in the browser nicely. You can

rename the log in the App.Config file.

Page 74: Dev Force 2010 Developers Guide

74 | P a g e

DevForce also supplies a sample utility, the TraceViewer, which comes in WinForms and WPF flavors.

The TraceViewer affords a friendlier and more dynamic look at logged activity as it provides a “live” view

of trace activity for your running application. You can launch the TraceViewer from the IdeaBlade

DevForce/Tools menu. It can also be linked directly into your application. See the Object Persistence

chapter for details.

Appendix: Listings of Sample App.Config Files

Listing 1. App.Config associated with the Domain Model

XML

<?xml version="1.0" encoding="utf-8"?>

<configuration>

<connectionStrings>

<add name="ServerModelNorthwindIBContext"

connectionString="metadata=res://*/ServerModelNorthwindIB.csdl|res://*/ServerModelNorthw

indIB.ssdl|res://*/ServerModelNorthwindIB.msl;provider=System.Data.SqlClient;provider

connection string=&quot;Data Source=.;Initial Catalog=NorthwindIB;Integrated

Security=True;MultipleActiveResultSets=True&quot;"

providerName="System.Data.EntityClient" />

</connectionStrings>

</configuration>

Listing 2. Copy of app.config associated with the executable project

XML

<?xml version="1.0"?>

<configuration>

<configSections>

<section name="ideablade.configuration"

type="IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core"/>

</configSections>

<ideablade.configuration version="6.00"

xmlns="http://schemas.ideablade.com/2010/IdeaBladeConfig">

<logging logFile="DebugLog.xml"/>

<!-- Additional configuration can be added to override defaults.

See the sample config files in the Learning Resources for more information.

-->

Page 75: Dev Force 2010 Developers Guide

75 | P a g e

</ideablade.configuration>

<connectionStrings>

<add name="ServerModelNorthwindIBContext"

connectionString="metadata=res://*/ServerModelNorthwindIB.csdl|res://*/ServerModelNorth

windIB.ssdl|res://*/ServerModelNorthwindIB.msl;provider=System.Data.SqlClient;provider

connection string=&quot;Data Source=.;Initial Catalog=NorthwindIB;Integrated

Security=True;MultipleActiveResultSets=True&quot;"

providerName="System.Data.EntityClient" />

</connectionStrings>

</configuration>

Appendix: Probing Sequence for the App.Config File Whenever a .NET application attempts to exercise any aspect of the DevForce API that requires

DevForce configuration information and such configuration information has not already been found and

placed into memory, DevForce will search for a configuration file that contains this information.

Its probing path is as follows:

1. If the ConfigFileLocation property of the IdeaBladeConfig object has been set in the executing code,

DevForce will search the indicated location for a file named or matching “*.exe.config” (or “web.config” if

a web project). If not found, other *.config files in the folder are searched for a valid

ideablade.configuration section.

2. If the ConfigFileLocation property was not set, or a suitable config file was not found in the indicated

location, then DevForce will look to see if the IdeaBladeConfig.ConfigFileAssembly property has been set.

If so, it will look for an embedded resource named “app.config”.

3. Next, the BaseAppDirectory is searched for a file named or matching “*.exe.config” (or “web.config” if a

web project). If not found, other *.config files in the folder are searched for a valid ideablade.configuration

section.

4. DevForce next searches for an embedded resource named “app.config” in the entry assembly.

5. If a valid Ideablade.configuration section was not found in any of the above locations then DevForce will

create a default IdeaBladeConfig instance. Check your debuglog.xml if you find that your configuration is

not being used.

Note that in a test project, such as one created with MSTest, there is no entry assembly so the

probing sequence may not correspond to what you’ll see in your non-test projects. You can work

around this by setting IdeaBlade.ConfigFileLocation or IdeaBlade.ConfigFileAssembly, or deploying

the config file to the test folder.

Page 76: Dev Force 2010 Developers Guide

76 | P a g e

DevForce Entity Model (EDMX)

Designer Enhancements

DevForce Entity Model (EDMX) Designer Enhancements ....................................................... 76

Entity-Model-Level DevForce-Specific Properties ............................................................................................. 77

Type-Level DevForce-Specific Properties ........................................................................................................... 79

Property-Level DevForce-Specific Properties ..................................................................................................... 80

DevForce is integrated seamlessly with the Entity Framework 4.0 EDMX Designer. For practical

purposes, this means that DevForce-specific properties now appear directly on their associated EDMX

Designer property pages alongside the standard EDMX designer properties. On each property page,

you will find these properties grouped under a “DevForce Code Generation” category heading.

Setting or modifying any of these properties will make a modification in the underlying EDMX file. All

DevForce-related changes are additive to what is already in the EDMX file and are generated as XML

attributes with a DevForce specific namespace qualifier. This insures that DevForce additions cannot

conflict with or break an EDMX file.

When an EDMX file is saved for the first time, a new project item is added to the project containing the

EDMX file. This project item has the same name as the EDMX file but with an added “.tt” extension.

Several additional files are also generated under this project item. One is a “ReadMe” file that explains

in more detail how to customize template generation. The other files have an “.IB.Designer” extension

and contain the DevForce-generated .NET code. Feel free to inspect these files, but do not modify them

because they will be regenerated each time the EDMX file changes. In most cases, there will only be a

single such file, but there are options (described later) to allow the generated code to span multiple

files.

One further side effect of this operation is to “turn off” the standard Entity Framework code generation

mechanism. DevForce does this by modifying the “Custom Tool” property associated with the “EDMX”

file by adding a “<RemoveToRestore>” prefix6 to the custom tool name specified there. At any time this

prefix may be removed to restore the standard Entity Framework code generation. If you do that, it is

usually a good idea also to remove the DevForce “.tt” file, along with its underlying children, to avoid

conflicts.

6 You will receive a compiler warning that the custom tool “<RemoveToRestore>EntityModelCodeGenerator” was not found. This message

is harmless. To eliminate it, you can entirely remove the Custom Tool value for the EDMX file.

Page 77: Dev Force 2010 Developers Guide

77 | P a g e

Every time the EDMX file is saved, the DevForce “.tt” file along with the associated generated .NET code

is recreated. The .tt file is a T47 template, and is used to auto-generate the DevForce domain model

files. DevForce code generation will occur under either of two conditions:

1. When the EDMX file is saved.

2. When the corresponding file with the same name as the EDMX file with a “.tt” extension has its

“Run Custom Tool” operation performed.

If you wish completely to disable the DevForce behaviors, the “Tools -> ExtensionManager” menu within Visual

Studio 2010 contains an entry for the “IdeaBlade OM Designer Extension” that can be disabled.

The remainder of this document will enumerate each of the DevForce EDMX properties. Each of these properties

will be available along with the rest of the Entity Framework properties whenever the corresponding “parent” object

is selected in the EDMX designer. These properties currently exist at three levels within the designer, each of which

is accessible by selecting the appropriate object in the designer:

1. At the model level

2. At the type level (Entity or Complex Type)

3. At the property level

Entity-Model-Level DevForce-Specific Properties

To see Entity-Model-Level, select white space on the designer surface.

DataSource Key (string)

Each EDMX file represents a single datasource; this field contains a unique name to identifying this

datasource. The value specified will be used to create [DataSourceKey] attributes in the generated

code. At runtime, the DataSourceKey will be used to find the appropriate connection string, from either

the connectionStrings or edmKeys elements in the configuration file.

7 T4 is shorthand for “Text Template Transformation Toolkit”. See, for example, the webcast “T4 Templates in Visual Studio

Code Generation” at http://www.pnpguidance.net/Screencast/T4TemplatesVisualStudioCodeGenerationScreencast.aspx .

Page 78: Dev Force 2010 Developers Guide

78 | P a g e

EntityManager Name (string)

This will be the name of the generated “EntityManager” subclass. Its value need not be unique within a

project. If a single project has multiple EDMX files, then any models with the same “Entity Manager

Name” will be generated as a series of partial class files together composing a single EntityManager

subclass. Otherwise, multiple EntityManager subclasses will be created.

C#

[IbEm.DataSourceKeyName(@"NorthwindIBEntities")]

public partial class NorthwindIBEntities : IbEm.EntityManager { ..}

Generate Binding Attributes (bool )

This setting controls whether to generate the following attributes onto individual properties of your

entities:

BindableAttribute, DisplayAttribute, EditableAttribute and ReadOnlyAttribute.

In generated, each of these attributes will be generated with the appropriate parameters for each

property of the entity. Note that most of these attributes will be generated based in part on other

properties within the EDMX file, i.e. property names, whether a setter exists, etc.

Generate Developer Classes (bool)

This setting allows you to have DevForce auto-generate developer “stub” files for all entities.

Generate Validation Attributes (bool)

Whether to generate .NET Validation attributes on applicable properties. Currently the relevant .NET

Validation attributes are the StringLengthAttribute and the RequiredAttribute.

Generate Verification Attributes (bool)

Whether to generate DevForce Validation attributes. If both GenerateValidationAttributes and

GenerateVerificationAttributes are true, then only the “DevForce” attributes will appear in the

generated code, but these attributes will be generated in such a way as to act exactly like the .NET

Validation attributes when standard .NET Validation calls are used. DevForce Verification will also work

as described in the Developer Guide. In general, DevForce Verification provides a richer validation

model and is more seamlessly integrated into the DevForce entities.

Injected Base Type (string)

This allows a developer to “inject” a new base entity type at the root of the EDMX class hierarchy. All

generated classes will either directly or through their base classes inherit from this class. This class will

usually be generated just like any other entity class, as a partial class definition within the generated

code that can be customized by the addition of other partial class files with additional implementation

details. This class will be generated automatically, except in the case where the type name specified

represents a “qualified” name. A qualified name is any name containing a “.”. In this case no partial

class definition will be generated but all other generated code will still derive from a class with the

Page 79: Dev Force 2010 Developers Guide

79 | P a g e

specified name. This feature allows a single “base” entity class to exist in a separate project with its own

namespace and still be shared among multiple EDMX models.

Max. Classes per File (number)

Models with a large number of entities can sometimes result in generated code files that are too large

to be processed by the Visual Studio editor. This problem may be avoided by generating code into more

than one file. The “Max classes per file” setting will limit the number of classes that are generated into a

single file and will create as many files as are necessary to meet the specified constraint for each file.

The default value for this property is 100 but can be adjusted in either direction. If it is set to 1, then

each generated file will contain only a single class. Having many generated files is often a bad idea

because of issues involved with synchronizing many changed files within a source control system. We

recommend leaving this default as is unless you have a specific issue that requires changing it.

Type-Level DevForce-Specific Properties

To see type-level properties, select any type on the designer surface (e.g., Customer):

Can Query (enum – True, False, Default)

Determines whether a client application (as opposed to Server side code), can perform queries that

return or involve this type. Setting this property to either “True” or “False” will cause a

“ClientCanQuery” attribute to be added to the generated code for the specified type. Setting the value

to “Default” will suppress the generation of this attribute. The interpretation of what “Default” means

is then deferred to any implementation of the EntityServerQueryInterceptor (see separate

documentation in the Security chapter).

C#

[IbEm.ClientCanQuery(true)]

public partial class Customer : IbEm.Entity { }

CanSave (enum – True, False, Default)

Same idea as CanQuery but applied to whether a client application can “save” entities of this type. The

EntityServerSaveInterceptor mediates this process. (See the Security chapter for more information on

implementing this class.)

Page 80: Dev Force 2010 Developers Guide

80 | P a g e

C#

[IbEm.ClientCanSave(true)]

public partial class Customer : IbEm.Entity { }

Property-Level DevForce-Specific Properties

To see property-level properties, select any property within a type (e.g., CompanyName within

Customer).

Bindable Mode (enum – OneWay, TwoWay, None)

If the GenerateBindingAttributes property defined above at the model level is set to true, then this

property describes how to construct the “BindableAttribute” for the property in the generated code.

C#

[Bindable(true, BindingDirection.TwoWay)]

public string CompanyName { .. }

Concurrency Strategy (enum – None, AutoGuid, AutoDateTime, AutoIncrement,

ServerCallback, Client)

Determines whether this field participates in Optimistic Concurrency conflict resolution and if so how

and where this column is updated. This topic is described in more detail in the Developer Guide. The

auto-generated DataEntityProperty constructor contains the strategy selected.

C#

public static readonly IbEm.DataEntityProperty<Customer, Nullable<int>> RowVersion = new

IbEm.DataEntityProperty<Customer, Nullable<int>>("RowVersion", true, false,

IbEm.ConcurrencyStrategy.AutoIncrement, false);

Display Name (string)

If the GenerateBindingAttributes property defined above at the model level is set to true, then this

property is used to construct the DisplayAttribute for the property in the generated code.

Page 81: Dev Force 2010 Developers Guide

81 | P a g e

C#

[Display(Name="CompanyName", AutoGenerateField=true)]

public string CompanyName { .. }

Page 82: Dev Force 2010 Developers Guide

82 | P a g e

Customizing the DevForce

Code Generation Template

DevForce code generation uses the .NET Text Template Transformation Toolkit (T4) to customize code

generation. DevForce ships with a default T4 template that is used for all code generation. Like most T4

templates provided by Microsoft and other vendors, this template can be replaced with a custom

version. Custom versions are usually a copy of the default template with some minor modifications.

While this mechanism is supported by DevForce, we provide what we believe to be a better approach:

we allow the developer to customize our template by subclassing it and overriding only those portions

from which he requires custom behaviors. This typically results in substantially less code to maintain. It

also means that as we release new and improved versions of the template with new features, customers

who have customized their templates are much less likely to be forced into corresponding changes.

Also unlike the templates offered by most other vendors, the T4 template with which DevForce ships,

and which your customized versions can leverage, has the ability to generate either C# or VB code from

a single template file. You will also see later that debugging a DevForce template is substantially easier

than debugging a standard T4 template.

A DevForce template looks something like the following:

T4

<#@ template language="C#" debug="true" hostSpecific="true" #>

<#@ output extension=".ReadMe" #>

<#@ Assembly Name="System.Core.dll" #>

<#@ Assembly Name="IdeaBlade.VisualStudio.DTE.dll" #>

<#@ Assembly Name="IdeaBlade.VisualStudio.OM.CodeGenerator.dll" #>

<#@ Assembly Name="IdeaBlade.EntityModel.Edm.Metadata.dll" #>

<#@ import namespace="System" #>

<#@ import namespace="System.Reflection" #>

<#@ import namespace="IdeaBlade.VisualStudio.DTE" #>

<#@ import namespace="IdeaBlade.VisualStudio.OM.CodeGenerator" #>

<#@ import namespace="IdeaBlade.EntityModel.Edm.Metadata" #>

<#

// Source for this file located at:

C:\Users\Jay\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\IdeaBlade, Inc\IdeaBlade

OM Designer Extension\6.0.1.0\DomainModelTemplate.tt

// Model1.edmx <--- This is needed so that "Transform Related Text Templates On Save"

works correctly.

var template = new DomainModelTemplate(this);

template.Generate();

#>

<#+

#>

There will be one such file added to a project for each EDMX file in the project. This file’s name will be

exactly the same as the EDMX file name but with a further “.tt” extension to indicate that it is a T4

template.

Page 83: Dev Force 2010 Developers Guide

83 | P a g e

If you have experience with T4 template files, you will notice that our template is much shorter than

most. That’s because our T4 template hands off the job of generating code to the

“DomainModelTemplate” class. This class is located in the “IdeaBlade.VisualStudio.OM.CodeGenerator”

assembly, and a copy of the source is provided in the DevForce installation directory.

Virtually every major code generation region within your generated code corresponds to a virtual

method within this class. To customize your generated code, several options are available:

1. Subclass the DomainModelTemplate class directly in the T4 template file provided

2. Copy the template that you have just customized to the directory named within the first comment within the

template.

3. Use approach #2, but separate the “DomainModelTemplate” subclass into a separate assembly that is then

referenced by the template.

We provide detail on these three alternatives below.

Option 1: Subclass the DomainModelTemplate class directly in the provided T4 template file

This might look something like the following:

T4

<#@ template language="C#" debug="true" hostSpecific="true" #>

<#@ output extension=".ReadMe" #>

<#@ Assembly Name="System.Core.dll" #>

<#@ Assembly Name="Microsoft.VisualStudio.TextTemplating.10.0" #>

<#@ Assembly Name="IdeaBlade.Core" #>

<#@ Assembly Name="IdeaBlade.VisualStudio.DTE.dll" #>

<#@ Assembly Name="IdeaBlade.VisualStudio.OM.CodeGenerator.dll" #>

<#@ Assembly Name="IdeaBlade.EntityModel.Edm.Metadata.dll" #>

<#@ import namespace="System" #>

<#@ import namespace="System.Reflection" #>

<#@ import namespace="IdeaBlade.VisualStudio.DTE" #>

<#@ import namespace="IdeaBlade.VisualStudio.OM.CodeGenerator" #>

<#@ import namespace="IdeaBlade.EntityModel.Edm.Metadata" #>

<#

// Source for this file located at:

C:\Users\Jay\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\IdeaBlade, Inc\IdeaBlade

OM Designer Extension\6.0.1.0\DomainModelTemplate.tt

// Model1.edmx <--- This is needed so that "Transform Related Text Templates On Save"

works correctly.

var template = new MyTemplate(this);

template.Generate();

#>

<#+

public class MyTemplate : DomainModelTemplate {

public MyTemplate(Microsoft.VisualStudio.TextTemplating.TextTransformation

textTransformation)

: base(textTransformation) {}

protected override void WriteEntityDataPropertyAttributes(EdmPropertyWrapper property) {

WriteAttribute("Foo(" + Quote(property.Name) + ")");

base.WriteEntityDataPropertyAttributes(property);

Page 84: Dev Force 2010 Developers Guide

84 | P a g e

}

}

#>

Modifications to the original template are in italicized bold. This is a very simple customization that adds

a “Foo” attribute to the top of every data entity property that is generated. Note that two additional

assembly references were added to the top of the template and that the call to “new

DomainModelTemplate” was replaced by a call to “new MyTemplate”. The MyTemplate class in this

case is provided directly below within the template itself.

While this is certainly simple, it is not the recommended approach for several reasons. Firstly, this

customization will only apply to the specific EDMX that it is “attached” to, and secondly it is difficult to

edit a template file with any significant amount of code because of the lack of Intellisense.

Option 2: Copy the customized template to the directory named within the first comment

The second approach to customizing the code generation process is to create a copy of the template that you have

just customized and copy it to the directory named within the first comment within the template. This location will

vary according to machine and operating system. In the above example we would copy the file to the following

directory:

C:\Users\Jay\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\IdeaBlade, Inc\IdeaBlade OM Designer Extension\6.0.1.0\

Within this directory there will be a file named “DomainModelTemplate.tt”. This is the current template

and it is usually a good idea to rename this file for backup purposes. The file that you just copied to this

directory should then be renamed to “DomainModelTemplate.tt”. Two additional edits will be needed

to complete the process. The two comment lines within the file should be replaced with the following

two comment lines. These can be copied directly from the original “DomainModelTemplate.tt” file.

T4

// Source for this file located at: $templateFullFileName$

// $edmxname$ <--- Needed so "Transform Related Text Templates On Save" works correctly.

Once this is done, any templates that are automatically added to a project will be based on your new

template. If you have older projects that were generated with an earlier version of the template, the

Page 85: Dev Force 2010 Developers Guide

85 | P a g e

“.tt” files in these directories can be removed and the EDMX files resaved to force a regeneration of

both the template file and your new generated code.

Option 3: Use Option2, but separate the “DomainModelTemplate” subclass into a separate

assembly that is then referenced by the template

A third approach to customization is based on the second approach, but separates out the “DomainModelTemplate”

subclass into a separate assembly that is then referenced by the template. The idea is to create your own assembly

with a single class that inherits from “IdeaBlade.VisualStudio.OM.CodeGenerator.DomainModelTemplate”. This

would look virtually identical to the “MyTemplate” class from above:

C#

public namespace MyNamespace {

public class MyTemplate : DomainModelTemplate {

public MyTemplate(Microsoft.VisualStudio.TextTemplating.TextTransformation

textTransformation)

: base(textTransformation) {}

protected override void WriteEntityDataPropertyAttributes(EdmPropertyWrapper property) {

WriteAttribute("Foo(" + Quote(property.Name) + ")");

base.WriteEntityDataPropertyAttributes(property);

}

}

And your template would then return to something very similar to the original template.

T4

<#@ template language="C#" debug="true" hostSpecific="true" #>

<#@ output extension=".ReadMe" #>

<#@ Assembly Name="System.Core.dll" #>

<#@ Assembly Name="IdeaBlade.VisualStudio.DTE.dll" #>

<#@ Assembly Name="IdeaBlade.VisualStudio.OM.CodeGenerator.dll" #>

<#@ Assembly Name="IdeaBlade.EntityModel.Edm.Metadata.dll" #>

<#@ Assembly Name=”MyAssembly” #> or <#@ Assembly Name=”{path to MyAssembly}” #>

<#@ import namespace="System" #>

<#@ import namespace="System.Reflection" #>

<#@ import namespace="IdeaBlade.VisualStudio.DTE" #>

<#@ import namespace="IdeaBlade.VisualStudio.OM.CodeGenerator" #>

<#@ import namespace="IdeaBlade.EntityModel.Edm.Metadata" #>

<#

// Source for this file located at: $templateFullFileName$

// $edmxname$ <--- This is needed so that "Transform Related Text Templates On Save"

works correctly

var template = new MyNamespace.MyTemplate(this);

template.Generate();

#>

<#+

#>

The two references to “MyAssembly” in the template above have to do with whether “MyAssembly” has been

placed in the “GAC” or not. If it has, then the first syntax is appropriate, otherwise, use the fully qualified path to

Page 86: Dev Force 2010 Developers Guide

86 | P a g e

your assembly. The advantage of this approach is that both debugging and construction, because of intellisense,

becomes much simpler.

Page 87: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

87 | P a g e

Property Interceptors

Property Interceptors ................................................................................................................... 87

State of the Release Candidate Documentation ................................................................................................... 88

Code Snippets in This Document ........................................................................................................................ 88

Attribute Interception........................................................................................................................................... 88

Named vs. Unnamed Interceptor Actions ............................................................................................................ 91

Interceptor Chaining and Ordering ...................................................................................................................... 92

Multiple Attributes on a Single Interceptor Action ............................................................................................. 94

Defining an Interceptor to Act Against Multiple Types ...................................................................................... 95

The EntityPropertyNames class ........................................................................................................................... 96

PropertyInterceptorArgs and IPropertyInterceptorArgs ...................................................................................... 96

PropertyInterceptor Attribute Discovery ........................................................................................................... 104

Alternative PropertyInterceptor Attribute Method Signatures ........................................................................... 106

Dynamic Property Interception and the PropertyInterceptorManager. .............................................................. 107

EntityProperties and Property Interceptors ........................................................................................................ 109

PropertyInterceptor Keys ................................................................................................................................... 109

Mechanics of Property Interception ................................................................................................................... 109

DevForce provides a mechanism to intercept and either modify or extend the behavior of any .NET

property. This interception is intended to replace, and expand upon, the technique of marking

properties as virtual and overriding them in a subclass. This facility is a lightweight form of what is

termed “Aspect-Oriented Programming”.

Interception can be accomplished either statically, via attributes on developer-defined interception

methods, or dynamically, via runtime calls to the ‘current’ instance of the PropertyInterceptorManager

(described later). Attribute interception is substantially easier to write and should be the default choice

in most cases.

Page 88: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

88 | P a g e

State of the Release Candidate Documentation

We are working hard to update all of our documentation from DevForce 2009 to DevForce 2010, .NET 4,

and Silverlight 4. During this conversion, you may find some sections that are out of date, but you should

be able to get many of the examples to work, with small modifications, by checking against the API

Documentation for the current method signatures.

The C# code snippets in this document (and the corresponding Visual Studio solution) are current for

DevForce 2010, but the VB snippets have not yet been updated. If you have questions about the

currency of particular material, please contact support.

Code Snippets in This Document The code snippets in this document are duplicated in accompanying C# and VB8 code solutions. After

installing DevForce, you will find these solutions in the _TopicDocumentSnippets folder under the

Business Object Persistence topic in the Learning Resources.

The captions associated herein with the snippets reflect the corresponding method names in the code

solutions. In this chapter two methods are often referenced:

1. The Entity class (e.g., Customer, Employee, etc.) where the interceptor is defined

2. A test method in MainDemo.cs that exercises the interceptor and demonstrates its effect.

Attribute Interception DevForce supplies four attributes that are used to specify where and when property interception should

occur. These attributes are

IdeaBlade.Core.BeforeGetAttribute

IdeaBlade.Core.AfterGetAttribute

IdeaBlade.Core.BeforeSetAttribute

IdeaBlade.Core.AfterSetAttribute

Under most conditions these attributes will be placed on methods defined in the custom partial class

associated with a particular DevForce entity. For example, the code immediately below represents a

snippet from the autogenerated Employee class.

(Generated code)

8 forthcoming

Page 89: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

89 | P a g e

C#

public partial class Employee : IdeaBlade.EntityModel.Entity {

public string LastName {

get { return PropertyMetadata.LastName.GetValue(this); }

set { PropertyMetadata.LastName.SetValue(this, value); }

}

VB

Partial Public Class Employee

Inherits IdeaBlade.EntityModel.Entity

Public Property LastName() As String

Get

Return LastNameEntityProperty.GetValue(Me)

End Get

Set(ByVal value As String)

LastNameEntityProperty.SetValue(Me, value)

End Set

End Property

Property interception of the get portion of this property would be accomplished by adding the following

code fragment to a custom Employee partial class definition:

Code Listing 1. Customer.UppercaseCompanyNameAfterGet(). Tested by MainDemo.TriggerBasicAfterGet().

C#

[AfterGet(EntityPropertyNames.CompanyName)]

public void UppercaseCompanyNameAfterGet(

PropertyInterceptorArgs<Customer, String> args) {

var companyName = args.Value;

if (!String.IsNullOrEmpty(companyName)) {

args.Value = args.Value.ToUpper();

}

}

VB

<AfterGet(EntityPropertyNames.LastName)> _

Public Sub UppercaseLastName(ByVal args As PropertyInterceptorArgs(Of Employee,

String))

Page 90: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

90 | P a g e

Dim lastName = args.Value

If Not String.IsNullOrEmpty(lastName) Then

args.Value = args.Value.ToUpper()

End If

End Sub

DevForce will insure that this method is automatically called as part of any call to the

Employee.LastName ‘get’ property. The “AfterGet” attribute specifies that this method will be called

internally as part of the ‘get’ process “after” any internal get operations involved in the get are

performed. The effect is that the LastName property will always return an uppercased result. For the

remainder of this document, methods such as this will be termed interceptor actions.

The corresponding ‘set’ property can be intercepted in a similar manner.

Code Listing 2. Customer.UppercaseCityBeforeSet(). Demoed by MainDemo. TriggerBasicBeforeSet().

C#

[BeforeSet(EntityPropertyNames.City)]

public void UppercaseCityBeforeSet(IbCore.PropertyInterceptorArgs<Customer,

String> args) {

var city = args.Value;

if (!String.IsNullOrEmpty(city)) {

args.Value = args.Value.ToUpper();

}

}

VB

<BeforeSet(EntityPropertyNames.LastName)> _

Public Sub UppercaseLastName(ByVal args As PropertyInterceptorArgs(Of Employee, String))

Dim lastName = args.Value

If Not String.IsNullOrEmpty(lastName) Then

args.Value = args.Value.ToUpper()

End If

End Sub

In this case we are ensuring that any strings passed into the ‘LastName’ property will be uppercased

before being stored in the Employee instance ( and later persisted to the backend datastore). Note that,

in this case, the interception occurs “before” any internal operation is performed.

In these two cases we have implemented an ‘AfterGet’ and a ‘BeforeSet’ interceptor. BeforeGet and

AfterSet attributes are also provided and operate in a similar manner.

Page 91: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

91 | P a g e

Named vs. Unnamed Interceptor Actions The property interception code snippets shown above were all examples of what are termed ‘Named’

interceptor actions, in that they each specified a single specific ‘named’ property to be intercepted. It is

also possible to create ‘Unnamed’ interceptor actions that apply to all of the properties for a specific

target type. For example, suppose that the following code were implemented in the Employee partial

class:

Code Listing 3. Product.BeforeSetAny(). Demoed by MainDemo.ExerciseUnrestrictedBeforeSet().

C#

[BeforeSet]

public void BeforeSetAny(IbCore.IPropertyInterceptorArgs args) {

if (!Thread.CurrentPrincipal.IsInRole("Administrator")) {

throw new InvalidOperationException("Only admistrators can change Product

data!");

}

}

VB

' Note that no parameter follows the BeforeSet attribute

<BeforeSet> _

Public Sub BeforeSetAny(ByVal args As IPropertyInterceptorArgs)

If Not Thread.CurrentPrincipal.IsInRole(“Administrator”) Then

Throw New InvalidOperationException(“Only admistrators can change data”)

End If

End Sub

The result of this code would be that only those users logged in as administrators would be allowed to

call any property setters within the Employee class.

A similar ‘set’ action might look like the following:

Code Listing 4. Customer.AfterSetAny(). Demoed by MainDemo.ExerciseUnrestrictedAfterSet().

Page 92: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

92 | P a g e

C#

[AfterSet]

public void AfterSetAny(IbCore.IPropertyInterceptorArgs args) {

LogChangeToCustomer(args.Instance);

}

VB

<AfterSet> _

Public Sub AfterSetAny(ByVal args As IPropertyInterceptorArgs)

LogChangeToEmployee(args.Instance)

End Sub

This would log any changes to the employee class.

Later in this document we will also describe how to define interceptors that apply across multiple types

as well as multiple properties within a single type.

Interceptor Chaining and Ordering Any given property may have more than one interceptor action applied to it. For example:

Code Listing 5. See #region in Customer class. Demoed in MainDemo.ExerciseChainedInterceptors().

C#

#region Chained Interceptors

[AfterGet(EntityPropertyNames.LastName)]

public void UppercaseLastName(PropertyInterceptorArgs<Employee, String> args) {

/// … do something interesting

}

[AfterGet(EntityPropertyNames.LastName)] // same mode (afterGet) and property name as

above

public void InsureNonEmptyLastName(PropertyInterceptorArgs<Employee, String> args) {

// … do something else interesting

}

[AfterGet] // same mode as above and applying to all properties on employee.

public void AfterAnyEmployeeGet(PropertyInterceptorArgs<Employee, Object> args) {

// … global employee action here

}

#endregion Chained Interceptors

VB

<AfterGet(EntityPropertyNames.LastName)> _

Public Sub UppercaseLastName(ByVal args As PropertyInterceptorArgs(Of Employee,

Page 93: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

93 | P a g e

String))

''' … do something interesting

End Sub

<AfterGet(EntityPropertyNames.LastName)> _

Public Sub InsureNonEmptyLastName(ByVal args As PropertyInterceptorArgs(Of Employee,

String))

' … do something else interesting

End Sub

<AfterGet> _

Public Sub AfterAnyEmployeeGet(ByVal args As PropertyInterceptorArgs(Of Employee,

Object))

' … global employee action here

End Sub

In this case, three different interceptor actions are all ‘registered’ to occur whenever the

Employee.LastName property is called.

To execute these actions, the DevForce engine forms a chain where each of the ‘registered’ interceptor

actions is called with the same arguments that were passed to the previous action. Any interceptor can

thus change the interceptor arguments in order to change the input to the next interceptor action in the

chain. The ‘default’ order in which interceptor actions are called is defined according to the following

rules.

1) Base class interceptor actions before subclass interceptor actions.

2) Named interceptor actions before unnamed interceptor actions.

3) Attribute interceptor actions before dynamic interceptor actions.

4) For attribute interceptor actions, in order of their occurrence in the code.

5) For dynamic interceptor actions, in the order that they were added to the PropertyInterceptorManager.

Because of the rigidity of these rules, there is also a provision to override the default order that any

interceptor action is called by explicitly setting its ‘Order’ property. For attribute interceptors this is

accomplished as follows:

Page 94: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

94 | P a g e

Code Listing 6. See #region in Customer class. Demoed in MainDemo.ExerciseChainedInterceptors().

C#

[AfterGet(EntityPropertyNames.ContactName, Order=1)]

public void ContactNameAfterGet03(IbCore.PropertyInterceptorArgs<Customer, String>

args) {

...

}

VB

<BeforeSet(EntityPropertyNames.LastName, Order:=-1.0)> _

Public Sub UppercaseLastName(ByVal args As PropertyInterceptorArgs(Of Employee, String))

End Sub

The ‘Order’ property is defined as being of type ‘double’ and is automatically defaulted to a value of

‘0.0’. Any interceptor action with a property of less that ‘0.0’ will thus occur earlier than any

interceptors without a specified order and any value greater that ‘0.0’ will correspondingly be called

later, and in order of increasing values of the Order parameter. Exact ordering of interceptor actions can

thus be accomplished.

Multiple Attributes on a Single Interceptor Action There will be cases where you want a single interceptor action to handle more than one property but

less than an entire class. In this case, it may be useful to write an interceptor action similar to the

following:

Code Listing 7. Customer.UppercaseName().

C#

[BeforeSet(EntityPropertyNames.CompanyName)]

[BeforeSet(EntityPropertyNames.ContactName)]

[BeforeSet(EntityPropertyNames.ContactTitle)]

public void UppercaseName(IbCore.PropertyInterceptorArgs<Employee, String> args) {

var name = args.Value;

if (!String.IsNullOrEmpty(name)) {

args.Value = args.Value.ToUpper();

}

}

VB

<BeforeSet(EntityPropertyNames.FirstName), BeforeSet(EntityPropertyNames.LastName),

BeforeSet(EntityPropertyNames.MiddleName)> _

Public Sub UppercaseName(ByVal args As PropertyInterceptorArgs(Of Employee, String))

Dim name = args.Value

Page 95: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

95 | P a g e

If Not String.IsNullOrEmpty(name) Then

args.Value = args.Value.ToUpper()

End If

End Sub

Defining an Interceptor to Act Against Multiple Types You can also define an interceptor to work across more than one type; e.g., against multiple entity

types. In this event, you will want to locate the interceptor in a separate (non-entity) class; and you will

need to tell DevForce explicitly to discover the interceptors therein in order to activate them.

Here’s an AfterGet interceptor that intercepts every Get against every property of the Employee and

Product entities. (Don’t overuse this technique, or you may slow down your app unacceptably!) The

interceptor checks to see if the property is a DateTime, or something derived from a DateTime, and that

it’s value is not null. If both things are true, it converts the DateTime to local time. (The interceptor

assumes that the DateTime has been stored in the database as a universal time.)

C#

[IbCore.AfterGet(TargetType = typeof(Employee))]

[IbCore.AfterGet(TargetType = typeof(Product))]

public static void UniversalGetInterceptor(IbEm.IEntityPropertyInterceptorArgs args) {

// breaking these out to demonstrate some of the info you have available...

string propertyName = args.EntityProperty.Name;

string propertyDataType = args.EntityProperty.DataType.ToString();

bool isDateTime = args.EntityProperty.DataType.IsAssignableFrom(typeof(DateTime));

bool valueIsNull = args.Value == null;

if (isDateTime && !valueIsNull) {

DateTime aDateTime = ((DateTime)args.Value);

aDateTime = aDateTime.ToLocalTime();

args.Value = aDateTime;

}

}

VB

Note that, since we defined the above interceptor in a non-entity class, we had to specify in the AfterGet

attributes the types against which it is to operate: here, Employee and Product.

DevForce won’t find interceptors defined outside entity classes automatically. Here’s how you let

DevForce know you’ve defined some interceptors in a class named “AuxiliaryPropertyInterceptors”:

Page 96: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

96 | P a g e

C#

IbCore.PropertyInterceptorManager.CurrentInstance.DiscoverInterceptorsFromAttributes(typeo

f(AuxiliaryPropertyInterceptors));

VB

The EntityPropertyNames class In all of the previous examples we have shown ‘Named” attributes specified with the form

“EntityPropertyNames.,PropertyName-. This is a recommended pattern that ensures type safety.

However, the following two attribute specifications have exactly the same effect:

C#

[BeforeSet(EntityPropertyNames.FirstName)]

// or

[BeforeSet(“FirstName”)]

VB

The ‘EntityPropertyNames’ reference is to a class that is generated (as multiple partial classes) inside the

designer code file associated with the EntityModel. Its primary purpose is to allow specification of

property names as constants. Note that because EntityPropertyNames is generated as a set of partial

classes, you can add your own property names to the class for any custom properties that you create.

PropertyInterceptorArgs and IPropertyInterceptorArgs Interceptor actions get all of the information about the context of what they are intercepting from the

single interceptor argument passed into them. This argument will obviously be different for different

contexts; i.e. a set versus a get action, a change to an employee versus a company, a change to the

FirstName property instead of the LastName property. Because of this there are many possible

implementations of what the single argument passed into any interceptor action might contain.

However, all of these implementations implement a single primary interface: IPropertyInterceptorArgs.

Every interceptor action shown previously provides an example of this. In each case, a single argument

of type PropertyInterceptorArgs<Employee, String> or of type IPropertyInterceptorArgs was passed

into each of the interceptor methods.

In fact, the type of the ‘args’ instance that is actually be passed into each of these methods at runtime is

an instance of a subtype of the argument type declared in the methods signature. For any interceptor

action defined on a DevForce entity, the actual args passed into the action will be a concrete

implementation of one of the following classes.

Page 97: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

97 | P a g e

DataEntityPropertyGetInterceptorArgs<TInstance, TValue>

DataEntityPropertySetInterceptorArgs<TInstance, TValue>

NavigationEntityPropertyGetInterceptorArgs<TInstance, TValue>

NavigationEntityPropertySetInterceptorArgs<TInstance, TValue>

The boldfaced characters above indicate whether we are providing interception to a get or a set

property, as well as whether we are intercepting a data or a navigation property.

In general, you can write an interception method with an argument type that is any base class of the

actual argument type defined for that interceptor. If you do use a base class, then you may need to

perform runtime casts in order to access some of the additional properties provided by the specific

subclass passed in at runtime. These subclassed properties will be discussed later.

The entire inheritance hierarchy for property interceptor arguments is shown below:

Assembly Where Defined Property Interceptor Arguments

IdeaBlade.Core IPropertyInterceptorArgs

IPropertyInterceptorArgs<TInstance, TValue>

PropertyInterceptorArgs<TInstance, TValue>

IdeaBlade.EntityModel DataEntityPropertyInterceptorArgs<TInstance, TValue>

DataEntityPropertyGetInterceptorArgs<TInstance, TValue>

DataEntityPropertySetInterceptorArgs<TInstance, TValue>

NavigationEntityPropertyInterceptorArgs<TInstance, TValue>

NavigationEntityPropertyGetInterceptorArgs<TInstance, TValue>

NavigationEntityPropertySetInterceptorArgs<TInstance, TValue>

The generic <TInstance> argument will always be the type that the intercepted method will operate on,

known elsewhere in this document and the interceptor API as the “TargetType”. The <TValue>

argument will be the type of the property being intercepted. i.e. ‘String’ for the ‘LastName’ property.

Note that the interceptor arguments defined to operate on DevForce entities break into multiple

subclasses with additional associated interfaces based on two primary criteria.

1) Is it a „get‟ or a „set‟ interceptor?

a. „get‟ interceptor args implement IEntityPropertyGetInterceptorArgs

b. „set‟ interceptor args implement IEntityPropertySetInterceptorArgs

Page 98: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

98 | P a g e

2) Does it involve a „DataEntityProperty‟ or a „NavigationEntityProperty‟?.

a. „DataEntityProperty‟ args implement IDataEntityPropertyInterceptorArgs

b. „NavigationEntityProperty‟ args implement INavigationEntityPropertyInterceptorArgs

The API for each of the interfaces above is discussed below.

IPropertyInterceptorArgs

The root of all property interceptor arguments is the IPropertyInterceptorArgs interface. Its properties

will be available to all interceptors.

C#

public interface IPropertyInterceptorArgs {

Object Instance { get; }

Object Value { get; set; }

bool Cancel { get; set; }

Action<Exception> ExceptionAction { get; set; }

object Tag { get; set; }

object Context { get; }

}

VB

Public Interface IPropertyInterceptorArgs

ReadOnly Property Instance() As Object

Property Value() As Object

Property Cancel() As Boolean

Property ExceptionAction() As Action(Of Exception)

Property Tag() As Object

ReadOnly Property Context() As Object

End Interface

In general the most useful of these properties will be the ‘Instance’ and ‘Value’ properties.

The ‘Instance’ property will always contain the ‘parent’ object whose property is being intercepted. The

‘Value’ will always be the value that is being either retrieved or set.

The ‘Cancel’ property allows you to stop the execution of the property interceptor chain at any point by

setting the ‘Cancel’ property to ‘true.

The ‘ExceptionAction’ property allows you to set up an action that will be performed whenever an

exception occurs anywhere after this point in the chain of interceptors.

The ‘Tag’ property is intended as a general purpose grab bag for the developer to use for his/her own

purposes.

The ‘Context’ property is used for internal purposes and should be ignored.

An example of using the ExceptionAction and Cancel is shown below:

Page 99: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

99 | P a g e

Code Listing 8. Order.LogExceptionsAndCancelSets(). Demoed in MainDemo.TestLoggingOfExceptionsAndAutoCancellationOfSets().

C#

/// Do not let any setters throw an exception. Instead, eat them,

/// log them, and cancel the remainder of the Set operation.

[IbCore.BeforeSet(Order = -1)]

public void LogExceptionsAndCancelSets(IbCore.IPropertyInterceptorArgs args) {

args.ExceptionAction = (e) => {

Common.LogManager.LogAnAction(string.Format("Here in {0}", e.Message));

args.Cancel = true;

};

}

public static void LogAnAction(string msg) {

//... some Logging action

}

VB

<BeforeSet> _

Public Sub BeforeSetAny(ByVal args As IPropertyInterceptorArgs)

' Do not let any setters throw an exception

' Eat them and log them, and cancel the remainder of the set operation.

args.ExceptionAction = Function(e) SetterExceptionHandler (e, args)

End Sub

Private Function SetterExceptionHandler(ByVal e As Object, _

ByVal args As IPropertyInterceptorArgs) As Object

LogException(e)

args.Cancel = True

Return Nothing

End Function

Note that we applied an explicit Order value less than 0 for this interceptor. Assuming that none of the

property-specific interceptors have an explicit Order defined, their Order value defaults to zero, this

interceptor will run first for all properties of the type on which it’s defined.

Page 100: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

100 | P a g e

In our samples, this interceptor happens to be defined on the Order type. Please note that there is no

relationship between that fact and the use of the Order parameter in the BeforeSet attribute. Two different

things!

Generic IPropertyInterceptorArgs

The following is a generic version of IPropertyInterceptorArgs where both the Instance and Value

properties are now strongly typed; otherwise it is identical to IPropertyInterceptorArgs.

C#

public interface IPropertyInterceptorArgs<TInstance, TValue> : IPropertyInterceptorArgs {

TInstance Instance { get; }

TValue Value { get; set; }

bool Cancel { get; set; }

Action<Exception> ExceptionAction { get; set; }

object Tag { get; set; }

object Context { get; }

}

VB

Public Interface IPropertyInterceptorArgs(Of TInstance, TValue)

Inherits IPropertyInterceptorArgs

ReadOnly Property Instance() As TInstance

Property Value() As TValue

Property Cancel() As Boolean

Property ExceptionAction() As Action(Of Exception)

Property Tag() As Object

ReadOnly Property Context() As Object

End Interface

IEntity PropertyInterceptorArgs and subclasses

Whereas the interfaces above can be used to intercept any property on any object, the argument

interfaces below are for use only with DevForce specific entities and complex objects. Each interface

below provides additional contextual data to any interceptor actions defined to operate on DevForce

entities.

The most basic of these is simply the idea that each property on a DevForce entity has a corresponding

“EntityProperty” ( discussed elsewhere in this guide).

C#

public interface IEntityPropertyInterceptorArgs : IPropertyInterceptorArgs {

EntityProperty EntityProperty { get; }

}

VB

Public Interface IEntityPropertyInterceptorArgs

Inherits IPropertyInterceptorArgs

ReadOnly Property EntityProperty() As EntityProperty

End Interface

Page 101: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

101 | P a g e

An example is shown below:

Code Listing 9. Customer.AfterSetAnyUsingIEntityPropertyInterceptorArgs().

C#

[IbCore.AfterSet]

public void

AfterSetAnyUsingIEntityPropertyInterceptorArgs(IbCore.IPropertyInterceptorArgs

args) {

// Cast the IPropertyInterceptorArgs to the entity-specific version, then

// values information available on the EntityProperty contained thereby.

var entityPropertyArgs = args as IbEm.IEntityPropertyInterceptorArgs;

if (entityPropertyArgs != null) {

Common.LogManager.LogAnAction(string.Format("Property [Customer.{0}] was set

to the value: [{1}]",

entityPropertyArgs.EntityProperty.Name, args.Value.ToString()));

}

}

VB

<AfterSet> _

Public Sub AfterSetAny(ByVal args As IPropertyInterceptorArgs)

Dim entityPropertyArgs = TryCast(args, IEntityPropertyInterceptorArgs)

If entityPropertyArgs IsNot Nothing Then

Log(“The “ + entityPropertyArgs.EntityProperty.Name + “ was set to the value:= “ +

args.Value.ToString())

End If

End Sub

The next two interfaces provide additional context based on whether the interceptor action being

performed is a ‘get’ operation or a ‘set’ operation.

For a get operation, IdeaBlade entities have a concept of possibly multiple versions, i.e. an original,

current, or proposed version, of an entity at any single point in time. It may be useful to know which

‘version’ is being retrieved during the current action. Note that the version cannot be changed.

C#

public interface IEntityPropertyGetInterceptorArgs : IEntityPropertyInterceptorArgs {

EntityVersion EntityVersion { get; }

}

VB

Public Interface IEntityPropertyGetInterceptorArgs

Inherits IEntityPropertyInterceptorArgs

ReadOnly Property EntityVersion() As EntityVersion

End Interface

Page 102: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

102 | P a g e

The DevForce EntityProperty is an abstract class with two concrete subclasses; a DataEntityProperty and

a NavigationEntityProperty ( discussed elsewhere in this guide). The next two

IEntityPropertyInterceptorArgs subinterfaces allow access to instances of one or the other of these

depending on whether the property being intercepted is a data or a navigation property.

C#

public interface IDataEntityPropertyInterceptorArgs : IEntityPropertyInterceptorArgs {

DataEntityProperty DataEntityProperty { get; }

}

VB Public Interface IDataEntityPropertyInterceptorArgs

Inherits IEntityPropertyInterceptorArgs

ReadOnly Property DataEntityProperty() As DataEntityProperty

End Interface

C#

public interface INavigationEntityPropertyInterceptorArgs : IEntityPropertyInterceptorArgs {

NavigationEntityProperty NavigationEntityProperty { get; }

}

VB

Public Interface INavigationEntityPropertyInterceptorArgs

Inherits IEntityPropertyInterceptorArgs

ReadOnly Property NavigationEntityProperty() As NavigationEntityProperty

End Interface

IPropertyInterceptorArgs Type Coercion

One of the first issues that a developer will encounter with writing interceptor actions that handle more

than one property is that it becomes difficult or impossible to use a concrete subtype as the argument

to the interceptor.

For example, imagine that we wanted to write a single action that handled two or more very different

properties each of a different type:

This could be written as follows:

Code Listing 10. Employee.ActionToBePerformedAgainstDifferentTypesV1().

C#

[BeforeSet(EntityPropertyNames.HireDate)] // hire date is of type datetime

[BeforeSet(EntityPropertyNames.FirstName)] // firstname is of type string

public void ActionToBePerformedAgainstDifferentTypesV1(IbCore.IPropertyInterceptorArgs args)

{

var emp = (Employee)args.Instance;

var entityProperty = ((IbEm.IDataEntityPropertyInterceptorArgs)args).EntityProperty;

//.. do some action with emp and entityProperty

}

VB

<BeforeSet(EntityPropertyNames.HireDate), BeforeSet(EntityPropertyNames.FirstName)> _

Public Sub ActionToBePerformedAgainstDifferentTypes (ByVal args As IPropertyInterceptorArgs)

Dim emp = CType(args.Instance, Employee)

Dim entityProperty = (CType(args, IDataEntityPropertyInterceptorArgs)).EntityProperty

.. do some operation with emp and entityProperty

End Sub

Page 103: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

103 | P a g e

But ideally we would prefer to write it like this, in order to avoid performing a lot of superfluous casts:

Code Listing 11. Employee.ActionToBePerformedAgainstDifferentTypesV2().

C#

[BeforeSet(EntityPropertyNames.HireDate)] // hire date is of type datetime

[BeforeSet(EntityPropertyNames.FirstName)] // firstname is of type string

public void StrangeAction(DataEntityPropertySetInterceptorArgs<Employee, Object> args) {

// no casting

var emp = args.Instance;

var entityProperty = args.DataEntityProperty;

.. some very baroque operation

}

VB

<BeforeSet(EntityPropertyNames.HireDate), BeforeSet(EntityPropertyNames.FirstName)> _

Public Sub StrangeAction(ByVal args As DataEntityPropertySetInterceptorArgs(Of Employee,

Object))

' no casting

Dim emp = args.Instance

Dim entityProperty = args.DataEntityProperty

.. some very baroque operation

End Sub

The problem is that, according to the rules of inheritance, the two concrete classes that this method will

be called with:

Type 1: DataEntityPropertySetInterceptorArgs<Employee, String>

Type 2: DataEntityPropertySetInterceptorArgs<Employee, DateTime>

…do not inherit from:

Type 3: DataEntityPropertySetInterceptorArgs<Employee, Object>

In fact, the only class or interface that they do share is:

IPropertyInterceptorArgs

So in order to allow this construction, DevForce needs to “coerce” each of ‘Type1’ and ‘Type2” into

‘Type3” for the duration of the method call.

Because DevForce does do this, any of the following arguments are also valid:

Type 4: DataEntityPropertySetInterceptorArgs<Entity, Object>

Type 5: DataEntityPropertySetInterceptorArgs<Object, Object>

Type 5: PropertyInterceptorArgs<Employee, Object>

… etc.

The basic rule for the type coercion facility is that any concrete type can be specified if its generic

version is a subtype of the generic version of the actual argument type that will be passed in.

Page 104: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

104 | P a g e

PropertyInterceptor Attribute Discovery In general, any interceptor method declared within a DevForce entity and marked with a property

interceptor attribute will be automatically discovered before the first property access.

PropertyInterceptors will most commonly be defined within the developer-controlled partial class

associated with each entity.

Property interceptors can also be defined on any base class and these will also be discovered

automatically.

In order to reduce the surface area of any entity class, a developer may not want to expose the

property interceptor methods directly on the surface of his or her class. To facilitate this, DevForce will

also probe any public inner classes of any entity class and will locate any property interceptors defined

there as well.

Example:

Code Listing 12. Employee.LastNameInterceptor().

C#

public partial class Employee : IdeaBlade.EntityModel.Entity {

// internal class just for property interceptors

public class PropertyInterceptorsDefinitions {

[BeforeGet(Employee.EntityPropertyNames.LastName)]

public static void LastNameInterceptor(IEntityPropertyInterceptorArgs args) {

}

[AfterSet]

public static void LoggingInterceptor(IEntityPropertyInterceptorArgs args) {

}

}

VB

Partial Public Class Employee

Inherits IdeaBlade.EntityModel.Entity

' internal class just for property interceptors

Public Class PropertyInterceptorsDefinitions

<BeforeGet(Employee.EntityPropertyNames.LastName)> _

Public Shared Sub LastNameInterceptor(ByVal args As IEntityPropertyInterceptorArgs)

End Sub

Page 105: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

105 | P a g e

<AfterSet> _

Public Shared Sub LoggingInterceptor(ByVal args As IEntityPropertyInterceptorArgs)

End Sub

End Class

Important note: Property interceptor methods defined on a class directly may be either instance or static

methods; whereas property interceptors defined on an inner class (or anywhere other than directly on the

entity class) must be static methods.

In the event that a developer wants to completely isolate his interception methods in another non-

entity-based class, then discovery will not occur automatically. In this case, the

DiscoverInterceptorsFromAttributes(Type targetType) method on the PropertyInterceptorManager class

may be used to force discovery of any specified type and all of its base types.

Attribute interceptors that are declared outside of the classes to which they apply must be further

qualified via the “TargetType” property as shown below:

Code Listing 13. AuxiliaryPropertyInterceptors.LoggingInterceptor().

C#

[AfterSet(Employee.EntityPropertyNames.LastName, TargetType = typeof(Employee))]

public static void LoggingInterceptor(IEntityPropertyInterceptorArgs args) {

// ...

}

VB

Public Class UnattachedInterceptor

<AfterSet(User.EntityPropertyNames.Name, TargetType:=GetType(User)> _

Public Shared Sub LoggingInterceptor(ByVal args As IEntityPropertyInterceptorArgs)

End Sub

End Class

Page 106: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

106 | P a g e

Alternative PropertyInterceptor Attribute Method Signatures While the property interceptor methods described previously allow a great deal of control over the

entire interception process, there are times when this is overkill. Sometimes all you really want is to do

is modify or inspect the incoming or outgoing values. In these cases, a simplified signature for an

interception method is also provided.

For example the following standard interceptor action:

Code Listing 14. Customer.UppercaseLastName().

C#

[AfterGet(EntityPropertyNames.LastName)]

public void UppercaseLastName(PropertyInterceptorArgs<Employee, String> args) {

var lastName = args.Value;

if ( !String.IsNullOrEmpty(lastName)) {

args.Value = args.Value.ToUpper();

}

}

VB

<AfterGet(EntityPropertyNames.LastName)> _

Public Sub UppercaseLastName(ByVal args As PropertyInterceptorArgs(Of Employee, String))

Dim lastName = args.Value

If Not String.IsNullOrEmpty(lastName) Then

args.Value = args.Value.ToUpper()

End If

End Sub

can also be written as

Code Listing 15. Customer.UppercaseLastNameV2().

C#

[IbCore.AfterGet(EntityPropertyNames.CompanyName)]

public String UppercaseLastNameV2(String companyName) {

if (!String.IsNullOrEmpty(companyName)) {

return companyName.ToUpper();

}

else {

return String.Empty;

}

}

VB

<AfterGet(EntityPropertyNames.LastName)> _

Public Function UppercaseLastName(ByVal lastName As String) As String

If Not String.IsNullOrEmpty(lastName) Then

Page 107: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

107 | P a g e

Return lastName.ToUpper()

Else

Return String.Empty

End If

End Function

In general, any property interceptor action that only inspects or modifies the incoming value without the

need for any other context can be written in this form. In fact, if the action does not actually modify the

incoming value, the return type of the interceptor action can be declared as void.

Dynamic Property Interception and the PropertyInterceptorManager. Property interceptors can be added and removed dynamically by making use of the

PropertyInterceptorManager and the PropertyInterceptor classes. Their API’s are shown below:

C#

public sealed class PropertyInterceptorManager {

public static PropertyInterceptorManager CurrentInstance { get; set; }

public void DiscoverInterceptorsFromAttributes(Type targetType)

public void AddAction(PropertyInterceptorAction interceptorAction)

public bool RemoveAction(PropertyInterceptorAction interceptorAction)

public IList<PropertyInterceptorAction<TArgs>> GetActions<TArgs>(Type targetType,

String targetName, PropertyInterceptorMode mode)

where TArgs : class, IPropertyInterceptorArgs

}

VB

Public NotInheritable Class PropertyInterceptorManager

Private privateCurrentInstance As PropertyInterceptorManager

Public Shared Property CurrentInstance() As PropertyInterceptorManager

Get

Return privateCurrentInstance

End Get

Set(ByVal value As PropertyInterceptorManager)

privateCurrentInstance = value

End Set

End Property

public void DiscoverInterceptorsFromAttributes(Type targetType) public void

AddAction(PropertyInterceptorAction interceptorAction) public Boolean

RemoveAction(PropertyInterceptorAction interceptorAction) public IList(Of

PropertyInterceptorAction(Of TArgs)) GetActions(Of TArgs)(Type targetType, String

targetName, PropertyInterceptorMode mode) where TArgs : class, IPropertyInterceptorArgs

End Class

Page 108: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

108 | P a g e

C#

public class PropertyInterceptorAction<TArgs> : PropertyInterceptorAction

where TArgs : class, IPropertyInterceptorArgs {

public PropertyInterceptorAction(Type targetType, String targetName,

PropertyInterceptorMode mode, Action<TArgs> action);

public PropertyInterceptorAction(Type targetType, String targetName,

PropertyInterceptorMode mode, Action<TArgs> action,

Double order, String key);

public Type TargetType { get; }

public String TargetName { get; } }

public PropertyInterceptorMode Mode { get; }

public String Key { get; }

public Double Order { get; }

public Type ArgsType { get; }

public Type InstanceType { get; }

public Type ValueType { get; }

public PropertyInterceptorAction<TArgs> ConvertTo<TArgs>()

where TArgs : class, IPropertyInterceptorArgs;

}

VB

Public Class PropertyInterceptorAction(Of TArgs As {Class, IPropertyInterceptorArgs})

Inherits PropertyInterceptorAction

public PropertyInterceptorAction(Type targetType, String targetName,

PropertyInterceptorMode mode, Action(Of TArgs) action)

public PropertyInterceptorAction(Type targetType, String targetName,

PropertyInterceptorMode mode, Action(Of TArgs) action, Double order, String key)

public Type TargetType {get;}

public String TargetName {get;}

End Class

public PropertyInterceptorMode Mode {get;}

public String Key {get;}

public Double Order {get;}

public Type ArgsType {get;}

public Type InstanceType {get;}

public Type ValueType {get;}

public PropertyInterceptorAction(Of TArgs) ConvertTo(Of TArgs)() where TArgs : class,

IPropertyInterceptorArgs

Since there is no public constructor for the PropertyInterceptorManager class, the only instance

available to the developer is via the ‘CurrentInstance’ property. This property will always have a value.

The current instance is the container for all currently ‘registered” interceptor actions.

PropertyInterceptorActions can be added to an entity type as follows:

C#

Employee.PropertyMetadata.Title.GetterInterceptor.AddAction(

IbCore.PropertyInterceptorTiming.Before, FormatTitleProperty);

VB

Page 109: Dev Force 2010 Developers Guide

IdeaBlade DevForce Property Interceptors

109 | P a g e

EntityProperties and Property Interceptors Within a DevForce application, every property interceptor has a GetterInterceptor and a

SetterInterceptor property. These properties can also be used to modify the property interceptor

actions associated with that property. Under the covers this is going through the

PropertyInterceptorManager mechanism described above, but the syntax is often simpler. For example:

Code Listing 16. ApplyInterceptorToEmployeeTitleDynamically().

C#

Employee.PropertyMetadata.Title.GetterInterceptor.AddAction(

PropertyInterceptorTiming.Before, args => args.Value = args.Value.ToUpper());

VB

Employee.AddressEntityProperty.SetterInterceptor.AddAction(PropertyInterceptorTiming.Before,

args => args.Value = AddZipCode(args.Value));

Employee.AddressEntityProperty.SetterInterceptor.AddAction(PropertyInterceptorTiming.Before,

Function(args) args.Value = AddZipCode(args.Value))

PropertyInterceptor Keys Every property interceptor action has a key that can either be specified via an optional attribute

property or dynamically when the action is first created. If no key is defined, the system will

automatically create one. This key will be used to identify an action for removal. The

PropertyInterceptorManager.RemoveAction(interceptorAction) attempts to find an interceptor that

matches the one passed in. This match requires that the TargetType, TargetName, Mode, and Key be

the same between the two interceptor actions.

Mechanics of Property Interception Property interception within DevForce is accomplished by dynamically generating compiled lamda

expressions for each interceptor action. DevForce interceptors are discovered (but not compiled) as

each entity class is first referenced. Runtime compilation of each property interceptor occurs lazily the

first time each property is accessed. After this first access, the entire code path for each property access

is fully compiled. Properties that are never accessed do not require compilation. The addition or

removal of interceptor actions after they have been compiled does require a new compilation the next

time the property is executed. This happens automatically.

Errors encountered during the compilation process will thus appear when a property is accessed for the

first time. These exceptions will be of type PropertyInterceptorException and will contain information

on the specific method that could not be compiled into a property interceptor action. These are usually

a function of a PropertyInterceptorArgs parameter type that is not compatible with the property or

properties being accessed.

Page 110: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

110 | P a g e

Business Object Persistence

Page 111: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

111 | P a g e

Business Object Persistence....................................................................................................... 110

State of the Release Candidate Documentation ................................................................................................. 114

Note: Code Snippets in This Document............................................................................................................. 115

Object Persistence Overview .............................................................................................................................. 115

The Big Picture .................................................................................................................................................. 115

DevForce and the ADO.NET EntityModel ....................................................................................................... 116

Locating the Physical Data Source with a Key .............................................................................................. 118

Support for POCOs (Plain Old CLR Objects) ................................................................................................... 118

Persistence Management Capabilities ............................................................................................................... 119

Retrieving business objects ............................................................................................................................ 119

The Entity Cache ........................................................................................................................................... 120

Business objects in motion ............................................................................................................................ 121

Creating new business objects ....................................................................................................................... 121

Saving and undoing business object changes ................................................................................................ 122

Offline Support .............................................................................................................................................. 122

Application Security ...................................................................................................................................... 123

Business Object Security ............................................................................................................................... 123

N-Tier Architecture ....................................................................................................................................... 124

Three-Tier Deployment ............................................................................................................................. 125

Model Choice by Configuration ................................................................................................................ 125

Conclusion ..................................................................................................................................................... 126

Entity Queries and Entity Navigation ................................................................................................................ 126

Entity Queries .................................................................................................................................................... 127

Query v. Method Syntax ................................................................................................................................ 136

LINQ .............................................................................................................................................................. 137

The DevForce Predicate Builder ................................................................................................................ 138

Example: Simulate an In() Clause Condition on a Distantly Related Entity ............................................. 144

The PredicateDescription Class ................................................................................................................ 145

Example: Given a Collection of Parent Entities, Retrieve the Related Children ....................................... 147

PassthruESQL Queries .............................................................................................................................. 148

Remote Service Method Call (RSMC) ...................................................................................................... 149

Page 112: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

112 | P a g e

Entity Navigation ............................................................................................................................................... 150

Parent-Child Navigation properties ........................................................................................................... 150

Navigation Properties in Silverlight........................................................................................................... 151

Deferred Retrieval ..................................................................................................................................... 156

Proactive Data Loads ................................................................................................................................. 157

Missing objects .......................................................................................................................................... 159

The Null Entity .................................................................................................................................................. 160

Asynchronous Communication with the Business Object Server ................................................................... 160

Asynchronous Queries ....................................................................................................................................... 161

IAsyncResult Asynchronous Pattern ................................................................................................................. 163

Asynchronous Fulfillment of Navigation Property Queries .............................................................................. 163

Canceling Pending Operations........................................................................................................................... 164

The EntityListManager ....................................................................................................................................... 164

Entity Caching ..................................................................................................................................................... 168

All Business Objects are Cached ....................................................................................................................... 168

Entity Ancestry and Organization of the Cache ............................................................................................. 168

Business objects are unique in each cache ..................................................................................................... 170

Entities in Lists .............................................................................................................................................. 170

Business object proper, not the business object graph ................................................................................... 171

Queries, Navigation, and the Cache ................................................................................................................... 171

Query Cache .............................................................................................................................................. 171

Primary key queries ................................................................................................................................... 172

“Object Not Found” and the Null Entity .................................................................................................... 172

Cache use when disconnected .................................................................................................................... 172

Modifications ............................................................................................................................................. 173

Stale Entity Data ........................................................................................................................................ 173

Fetch Life Cycle Events ............................................................................................................................. 174

Query Workflow ................................................................................................................................................ 174

Query Strategy ................................................................................................................................................... 176

Fetch Strategies .......................................................................................................................................... 177

MergeStrategies ......................................................................................................................................... 178

InversionMode ........................................................................................................................................... 179

Pre-Defined QueryStrategies ..................................................................................................................... 181

Page 113: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

113 | P a g e

Custom QueryStrategies ............................................................................................................................ 182

DefaultQueryStrategy ................................................................................................................................ 183

When to Use The Different QueryStrategies ............................................................................................. 183

Making a One-Time Change to the QueryStrategy With Which a Given Query Is Run ........................... 184

Span Queries ...................................................................................................................................................... 185

Performance Details ................................................................................................................................... 187

Cached Entity Lifespan...................................................................................................................................... 188

Saving the Cache Locally .................................................................................................................................. 188

The TraceViewer: Watch What Data Is Being Loaded, and How ..................................................................... 189

Using the Trace Viewer Stand-Alone ............................................................................................................ 190

Embedding the Trace Viewer in Your Application ....................................................................................... 193

Embedding the WPFTraceViewer in Your WPF App ................................................................................... 193

Embedding the WinTraceViewer in Your WinForms App ........................................................................... 195

Getting Generated SQL to Display in the TraceViewer ................................................................................ 199

Using the Trace Viewer With a Silverlight App ............................................................................................ 200

Creating Business Objects .................................................................................................................................. 202

When Not to Create ........................................................................................................................................... 202

The Business Object Create Method ................................................................................................................. 203

Generating unique identifiers......................................................................................................................... 203

GUIDs ........................................................................................................................................................ 204

Store-Generated Ids ................................................................................................................................... 204

Custom id generation ................................................................................................................................. 204

Ids in mapping objects ................................................................................................................................... 206

Creating a valid business object ..................................................................................................................... 207

Auxiliary Business Object Class Methods ......................................................................................................... 208

CompareTo() ............................................................................................................................................. 208

ToString() .................................................................................................................................................. 208

Adding and Removing Related Objects using Add() and Remove() ................................................................. 208

Business Object Creation Review ...................................................................................................................... 211

Saving Business Objects ...................................................................................................................................... 211

EntityState of an Object ..................................................................................................................................... 211

Undo .................................................................................................................................................................. 212

Validation .......................................................................................................................................................... 212

Page 114: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

114 | P a g e

Temporary Id Fix-up ......................................................................................................................................... 212

Life Cycle Events .............................................................................................................................................. 213

Client-Side Life Cycle Events ....................................................................................................................... 213

Saves and Transaction Management .................................................................................................................. 215

Distributed Transactions ............................................................................................................................ 216

Re-query After Save .......................................................................................................................................... 216

When Save Fails ................................................................................................................................................ 217

SaveChanges() Exceptions ............................................................................................................................ 217

EntityManagerSaveException ................................................................................................................... 218

SaveResult ................................................................................................................................................. 218

Alternatives to Default SaveChanges Exceptions .......................................................................................... 219

Data Source Concurrency .................................................................................................................................. 219

Saving the “Dependency Graph” ....................................................................................................................... 226

Association Types ...................................................................................................................................... 227

Compositions ............................................................................................................................................. 227

Save the Root Entity .................................................................................................................................. 228

Saving Event Handler ................................................................................................................................ 228

Composition Business Rules ..................................................................................................................... 229

Concurrency Violations ............................................................................................................................. 229

Dependency Graph Retrieval ............................................................................................................................. 229

Workflow For a Save ......................................................................................................................................... 232

Saving the Cache to a Local Disk File ............................................................................................................... 233

XML Serialization of Business Objects ............................................................................................................. 234

State of the Release Candidate Documentation

We are working hard to update all of our documentation from DevForce 2009 to DevForce 2010, .NET 4,

and Silverlight 4. During this conversion, you may find some sections that are out of date, but you should

be able to get many of the examples to work, with small modifications, by checking against the API

Documentation for the current method signatures.

The C# code snippets in this document (and the corresponding Visual Studio solution) are current for

DevForce 2010, but the VB snippets have not yet been updated. If you have questions about the

currency of particular material, please contact support.

Page 115: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

115 | P a g e

Note: Code Snippets in This Document The code snippets in this document are duplicated in accompanying C# and VB code solutions. After

installing DevForce, you will find these solutions in the _TopicDocumentSnippets folder under the

Business Object Persistence topic in the Learning Resources.

The captions associated herein with the snippets reflect the corresponding method names in the code

solutions. You will find these methods in the Program.cs or Main.vb files, respectively, for the C# and VB

solutions.

Object Persistence Overview

In previous chapters you’ve seen how object mapping declares relationships between business objects

and remote data sources. You learned that it generated classes for each business object as well as some

helper classes such as EntityRelations. The collection of these classes constitutes the application’s

business object model.

In this chapter we describe how the DevForce persistence scheme works with the business object

model.

You will learn that instances of the business object class (AKA the entity class) are held in a container

called the entity cache. This cache belongs to and is managed by an instance of the DevForce

EntityManager class.

You will discover that an EntityManager instance is rich in capabilities that go beyond retrieving and

saving business objects. We’ll introduce them here and elaborate on a few of them in subsequent

sections.

By the end of the chapter, you will appreciate that the EntityManager class is one of the most

important and useful classes in the DevForce framework.

The Big Picture A DevForce application relies upon a layered architecture for data access.

At one end is a data source – typically a relational database. At the other end is the user interface which

works with business objects in a business object model. There are several components in the middle.

Page 116: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

116 | P a g e

Figure 2. Cross-tier flow of data and business objects.

One of them, called an EntityServer, moves data (and data requests) between the ADO.NET Entity

Framework and the DevForce EntityManager. The EntityServer is responsible for issuing query and

save requests to the ADO.NET Entity Framework . It also manages application security. You can learn

more about the additional features of the EntityServer in the “Business Object Server” and “Security”

documents.

The EntityServer is an important component and you should understand its role in the object

persistence process. That said, you will seldom see or deal with it directly.

The second important DevForce component is the EntityManager. The EntityManager takes

instruction from the higher levels of the application such as the UI, and forwards UI requests for entities

to the EntityServer. The EntityManager puts the received entities – obtained from whatever source

by the EntityServer -- into its entity cache and makes them available to the UI.

End users review the entities and make changes through the UI. The UI signals the EntityManager to

save the changes. The EntityManager dutifully forwards the changed entities to the EntityServer

which communicates with the appropriate component to commit the data into persistent storage.

DevForce and the ADO.NET EntityModel Visual Studio’s ADO.NET Entity Data Model wizard creates an EDMX file which contains descriptions of a

conceptual data schema (the object model), an actual data store schema (the database model), and the

mappings between the two. The DevForce OM Designer Extension to the EDM Designer provides for

customization of the model with additional DevForce attributes, and also generates the object model in

Page 117: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

117 | P a g e

.NET code in a file named <ModelName>.IBDesigner.cs (or .vb). We generally refer to the generated

model as the “Domain model”, since DevForce allows for multiple data sources to be combined into a

single logical model. You can learn more about building the Domain Model in the “Business Object

Mapping” documents. The Domain model is “persistence ignorant”: unlike the Entity Framework model,

it has no knowledge whatsoever of the back-end data store or the mapping between that and its

objects. In an n-tier deployment, it is the only model that is deployed client side. The client needs no

connection information for back-end data sources.

A copy of the assembly containing the Domain model is also deployed server-side in an n-tier

deployment.

Architecture of the DevForce Business Object Class

The (partial) inheritance hierarchy for a DevForce business class is as follows:

Figure 3. Inheritance Hierarchy for a DevForce Business Class

The class for a business type is generated as one or two partial classes. In the partial classes labeled in

the picture as DevForce-controlled, the essential data structure of the type is defined. This partial class is

driven by settings in the domain model and gets regenerated whenever the EDMX is saved. Thus it

should never be modified by the developer.

All DevForce-controlled partial classes for types

originating from a given Entity Data Model are generated

into a single file, named

<EntityModelName>.IBDesigner.cs (or .vb, if generated

in Visual Basic rather than C#).

If the domain model includes multiple Entity Models,

one such code file will be generated for each model.

The partial class described in Figure 3 as “Developer-controlled” is optional, and can be generated by the

DevForce OM Extension in a one-time operation, or hand-coded by the developer. In either case, once it

exists, DevForce will not overwrite or modify it. The developer-controlled partial class is the developer’s

workshop, where he can add custom properties, methods, and events, as well as create property

interceptors9 to change the getter/setter behavior of properties defined in the DevForce-controlled

partial class.

9 See the Developers Guide chapter on “Property Interceptors”

Page 118: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

118 | P a g e

Modifying the behavior of a generated property

DevForce provides a mechanism to intercept and either modify or extend the behavior of any .NET

property, including, of course, those generated into the DevForce-controlled partial business classes.

You can accomplish virtually any desired behavior modification of property getters or setters via this

interception mechanism. The mechanism replaces, and expands upon, the technique of marking

properties as virtual and overriding them in a subclass. This facility is a lightweight form of what is

termed “Aspect-Oriented Programming”.

You can find detail about this in the chapter, “Property Interceptors”.

Locating the Physical Data Source with a Key How does the EntityServer locate the physical storage to use?

You learned earlier that every business object – every concrete entity – is mapped to a particular data

source. That data source is identified symbolically by a data source key. That key is compiled into the

entity and cannot be changed at run-time.

The EntityServer has a copy of the business object model so it knows the data source key for each

kind of business object. But the key is purely symbolic. It does not contain the location of a physical data

source nor can it determine how to connect to such a data source. It does not contain a database

connection string, for example.

Fortunately, the EntityServer also has a private copy of the application configuration file. It can use

the data source key to find in that file the physical data source configuration information it needs such

as the connection string for the physical data source it should use.

This is all we need to know for the moment to assure ourselves that a DevForce application actually can

move data between a physical data source and business objects in the client application. We turn next

to the EntityManager which is the keeper of business objects on the client side.

Support for POCOs (Plain Old CLR Objects) DevForce supports POCOs: instances of such objects can be queried, cached, updated, and saved just

like DevForce entities. Consider Figure 4. DevForce EntityManager Support for POCOs. The business

entity you saw diagrammed earlier in Figure 3. Inheritance Hierarchy for a DevForce Business Class is

now shown wrapped by a DevForce EntityWrapper. Alternatively, a POCO is wrapped. The abstraction of

the EntityWrapper permits the DevForce EntityManager to work with either type of object.

Page 119: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

119 | P a g e

Figure 4. DevForce EntityManager Support for POCOs

POCOs are discussed in detail later in this chapter (“POCO Support in DevForce”).

Persistence Management Capabilities In this section we introduce the most important capabilities of the EntityManager. Some topics deserve

extended attention and are discussed more fully in later chapters but you’ll get a preview here of how

DevForce persistence management can

retrieve business objects from data sources

manage them in its cache

move business objects across the Internet

create new business objects

save additions, changes, and deletions to a data source

restore pending changed and deleted objects to their retrieved state

continue to function when disconnected (even in Silverlight!)

preserve cache contents temporarily in local storage

log in and log out of the central server

ensure business object security, and

exploit an n-tier architecture.

Retrieving business objects DevForce applications deal in business objects. Accordingly, the DevForce retrieval mechanisms return

business objects. There are two such mechanisms: entity queries and entity navigation.

Page 120: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

120 | P a g e

An entity query hunts for objects with attributes that match specified search criteria. Suppose you need

a list of employees over 40. In DevForce you could express this criterion in a LINQ-To-DevForce query

which could be enumerated over to return a collection of Employee entities that happens to include

sales representative “Nancy Davolio.”

Entity navigation involves traversing from one kind of business object to another along a relation

between them. You can navigate from a sales order to “Nancy” with an expression such as

anOrder.SalesRep. This returns an Employee entity.

Entity navigation can return a collection of entities as well. The expression aSalesRep.Orders returns

the orders assigned to this employee sales rep. The orders are returned in special kind of generic list

whose contents are managed by the EntityManager, a feature you’ll find especially useful in your UI.

The section “Entity Queries and Entity Navigation” offers greater detail.

The Entity Cache A EntityManager caches business objects both for performance and to enable offline operation of the

application.

Each instance of EntityManager has its own cache of entities. Entities enter the cache in one of three

ways:

from a data source as a result of entity query or entity navigation

by creation as new entities

by import from another EntityManager or outside source

Most entities enter the cache from a data source. Standard entity queries and entity navigations check

the cache first to see if the desired objects are present; they resort to the data source only if the objects

are not found10. This behavior is usually desirable as it improves performance. The risk is that the

entities in the cache become stale. The programmer can, at his election, by-pass the cache and query

the database directly (the query results still end up in the cache). There are a host of other options

which are addressed in the section “Entity Queries and Entity Navigation”.

After a successful query, the cache holds the root business objects of the result. If you searched for

employees, the cache will hold employee entities. The cache may hold other related entities as well. But

it may not, and you shouldn’t assume that it holds the entire business object graph of an employee after

retrieving that employee. For example, after querying for “Nancy Davolio”, she is in the cache, but the

Orders for which she is responsible as Sales Rep probably are not.

10 DevForce keeps a cache of query objects for use in determining whether requested objects are already in the cache; we‟ll

cover this in more detail later.

Page 121: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

121 | P a g e

A cache holds at most one copy of a business object. Recall that a business object has a unique identity

implemented as a unique primary key. There is only one Employee in the application universe with an Id

= 42. If follows that there can only be one Employee in the cache with Id = 42.11

Finally, entities stay in the cache until the application terminates or they are removed explicitly. If your

application retrieves a great deal of data, you may have to take steps to prevent cache overflow, and a

variety of facilities are provided to assist with this. However, for most applications this never even

becomes an issue.

We’ll have more to say about caching in the coming pages.

Business objects in motion The EntityServer and EntityManager exchange data in a highly optimized manner. Because of our

efficient, automatic, and as-needed dehyration and rehydration of objects, as well as our seamless

interaction with the Microsoft Entity Framework and its objects, your experience of the exchange of

data between logical tiers is that it is simply DevForce business objects that are moving back and forth.

A DevForce business object sent from the EntityManager to the EntityServer, or vice versa, is, in all

important respects, exactly the same object when it arrives as when it left. It is of the same type, with

the same persistable field values, properties, methods, and events. In practical effect, the entire object

has traveled over the network; it is a “mobile business object.”

There are two important implications.

Developers write one business object class with the full capacity to execute on either the client or server as

required. They don‟t write one kind of object for the server and a different one for the client. They write

one class, period.

The application can be deployed on one physical tier, two tiers, three, or n-tiers – without recoding.

We guarantee complete object fidelity for cross-process or cross-machine communication, achieving this

through a combination of storage format, serialization methods, transport mechanisms, and data merge

facilities.

Creating new business objects The developer can make a new entity by invoking either a constructor or a factory method that returns

an instance of the business object. For most circumstances we recommend the latter technique, since it

permits you fully to control the details of the instantiation (such as initialization of required properties).

11 An application can actually have more than one EntityManager instance, though this is a needed only in sophisticated

applications and for special purposes. Each EntityManager instance will have its own cache, and each cache can contain an

instance of any given business object. But every entity instance knows its own EntityManager. If we ever encounter

two Employee entities with Id = 42, we can ask them “who is your EntityManager?”

For more information on the use of multiple EntityManagers, see the section “Multiple Entity Manager Instances” under

“Advanced Business Object Concepts”. For the balance of the current discussion, we will assume the application uses just

one EntityManager instance.

Page 122: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

122 | P a g e

You write the factory method, and typically call it Create, making it a public static (Shared in VB)

method of the business object’s developer-controlled partial class; e.g., Employee.Create().

There are four steps to the typical Create method:

Get a prototype instance of the new entity from the EntityManager

Give the prototype a unique identity

Initialize some of its values

Add the completed prototype to the EntityManager‟s cache

We explore these steps in the section “Creating Business Objects”.

Saving and undoing business object changes Adding, changing, and deleting are operations affecting business objects in a EntityManager cache only.

They are purely local modifications. They have no effect on the database and are invisible to other

application users.

The developer updates the database by telling the EntityManager to save changes. The developer can

save an individual entity, an arbitrary list of entities, or all entities with pending changes.

The wise developer will validate the business objects before saving them.

The developer can also undo the changes in which case the affected business objects are restored to

their state when last retrieved.

We explore these summary remarks with greater depth in the section “Saving Business Objects”.

Offline Support A client application can lose its connection to the central servers. The interruption may be brief, sudden,

and unexpected, as when a mobile device loses its signal; or it may be voluntary and last for hours, as

when the user runs the application offline on an airplane.

An application which is susceptible to connection failures is called a “partially connected” or

“intermittently connected” application.

A DevForce smart client application can operate when disconnected -- whether suddenly and

unexpectedly or on purpose -- for any length of time. It can be shut down and re-started without

skipping a beat.

While disconnected, the application can still create new objects and modify or delete cached entities.

Such changes accumulate in the cache until the application reconnects and performs a save.

All it takes is a little programming using some simple DevForce EntityManager features.

Page 123: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

123 | P a g e

Step #1: Manage the connection. The developer can control voluntary connection to the host and

respond to unexpected disconnects with the help of a small number of EntityManager properties,

methods, and events.

Step #2: Save a copy of the cache locally. The typical sequence is:

1. Fill the cache with entities that will be needed while running disconnected.

2. Disconnect and continue running.

3. Save the cache to the client‟s local storage (e.g., a file) just before exit.

4. Shut down.

5. On re-launch, restore the cache from the client copy.

All pending changes are preserved across the two sessions.

See the “Saving the Cache Locally” section of the “Business Object Caching” chapter to learn more.

Application Security We’ll devote a later chapter to securing your application, so we’ll just mention the topic briefly in this

overview.

Application security has three aspects:

Confidentiality

Authentication

Authorization

Confidentiality – A secure application guards against eavesdroppers intercepting and reading traffic

flowing between client and host. DevForce supports a variety of encryption measures including standard

SSL. They are discussed in the Security chapter of this Developers Guide.

Authentication – A secure application employs an authentication scheme to ensure that both parties to

a connection are who they claim to be. In a smart-client context there are two authentication burdens:

(1) the server must confirm and remain confident it is talking to a real, authorized client and (2) each

client must be confident it is conversing with an authentic server. DevForce has mechanisms to support

both kinds of checks.

Authorization –The EntityManager’s Login method stamps the client-side application thread with a

Principal object representing the authenticated user. This Principal has an IsInRole method that

returns true if the user participates in a named role passed to it. The developer has total flexibility in

determining the implementation of the login method, the IPrincipal object returned from it, and the

definition and usage of the role scheme.

For its own part, DevForce maintains a tamper-proof SessionBundle object that is used to authenticate

every transaction between the EntityManager and EntityServer.

Business Object Security

Page 124: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

124 | P a g e

A secure application prevents improper access to data in the data source.

The first step is to remove connection strings from the client. Connection strings have database

addresses and passwords. There is no disguising or hiding them on the client. They belong in a safe place

on the server.

The EntityManager doesn’t connect to the data source so it doesn’t need connection strings. It tells the

EntityServer which data source to use by sending a symbolic data source key. The key is just a name.

The EntityServer knows how to use the key to find and connect to the data source. No process on the

client side can use it.

A secure application provides more fine-grained security than just whether or not the client can access

the data source. It should prevent certain users from retrieving certain business objects. It should

discriminate among users in determining which kinds of data source update are permitted. The

screening could be at any level of detail from, say, the tables, down to a single column of a particular

record.

Spoofing

In n-tier applications, whether browser-based or smart client, there is always a risk that some process

pretending to be a valid client will attempt access the database in an unauthorized way. A good security

design assumes that the client process -- because it cannot be physically secured -- will be compromised.

While it may not be possible to fully protect the client, you can secure the host by deploying the

DevForce Business Object Server (BOS) which includes the full-scale version of the EntityServer. The

BOS will run special security methods whenever the client attempts to reach the server.

As discussed above, the EntityServer includes interception points for both queries and saves in the

EntityServerQueryInterceptor and EntityServerSaveInterceptor. Through these, you can

authorize and modify requests and results. Finally, DevForce business objects can be digitally signed

before transmission to the client. A rogue client cannot order the server to update the data source with

an imposter entity.

N-Tier Architecture We discussed n-tier architecture at the beginning of this chapter. “The Big Picture” topic described three

data management tiers:

1. Data source(s) on the data tier

2. EntityServer(s) as the data access tier

3. EntityManager within a client tier

You can run all three logical tiers on the client machine if you have a totally stand-alone application. This

is the preferred choice for most development work because it eliminates the complexities of

coordinating with other people, software, and hardware.

Page 125: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

125 | P a g e

When a data-driven application is deployed for production use, the database must reside on a central

tier so that many users can share the data efficiently. If, with the database so deployed, you put both

the EntityManager and an EntityServer in the same process running on a client PC, you have the ever

popular two-tier, “client/server” model.

This simplifies the exchanges between an EntityServer and the EntityManager. The two components

don’t have to communicate over a process boundary, so in a DevForce application deployed thusly, a

light-weight version of the EntityServer reads and writes directly to the EntityManager cache.

An EntityServer running under such circumstances cannot provide any meaningful security or

monitoring services. It serves simply a data access abstraction – a job it does very well.

Three-Tier Deployment

Enterprise-grade applications will deploy the logical tiers on three separate physical tiers: a database

server, an application server, and PC client machines. The application server hosts the Business Object

Server (BOS) which runs multiple instances of a more muscular version of the EntityServer.

This three-physical-tier deployment provides some remarkable advantages over the two-tier model. You

get:

Improved performance over connections slower than a local area network (e.g., the internet). The slow,

heavy work takes place between the BOS and the database over a fat, fast pipe. Communications and

data passing between the client and the middle tier are concise, compact, and highly optimized.

Application Reach -- Because the application can be on-line wherever there is an Internet connection

and without resort to VPN, it can be deployed and used by larger numbers and with reduced system

requirements. Whereas SQL commands and result sets – the raw data exchanged between a database

and a client-side access layer – cannot flow over web protocols, DevForce’s business objects can.

Security is much tighter. We covered earlier the many layers of security available with the BOS in place.

Scalability. It is impractical to maintain live connections for each client when the number of

simultaneous users becomes large. The tipping point appears to be around one hundred. An

EntityServer running on a central server can pool connections to the data sources and serve many

clients simultaneously. The server is stateless – there is no need for session awareness – so fail-over and

load balancing are easy options.

Model Choice by Configuration

One-tier? Two-tier? Three-tier? You don’t have to make the choice right away. You write our code pretty

much the same way no matter what the model. In general, you don’t have to think about which code is

executing where, or by what route our business objects arrived in cache. For the most part, you write

code as if every aspect of the application takes place inside your development PC.

Page 126: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

126 | P a g e

When you are ready to deploy to a multi-tiered environment, you set a few values in the XML

application configuration file (App.Config) and build some set-up projects.

Conclusion We just took a high-level view of the persistence management landscape. Some of the key points were:

The EntityManager is perhaps the most important component in the DevForce framework. It is the client

application‟s gateway to the remote data.

The EntityManager holds and manages an entity cache of business object instances and makes them

available to the application UI.

All entities within a cache are unique; no two entities can have the same primary key.

You can fetch entities into the cache from remote data sources using entity queries and entity navigation.

Entity navigation returns a collection whose contents are managed dynamically by a EntityManager.

You can create, modify, delete, remove, and save cached entities. These actions raise “Life Cycle” events

to which you may subscribe.

Entities in a cache can come from many different data sources. Each data source is identified by its data

source key. Each entity belongs to just one data source.

A smart-client application can run off-line.

An EntityServer handles the data access and object map translation chores for each of the application

data sources. It exchanges business objects with one or more EntityManager instances on individual

client machines.

A Business Object Server (BOS) running on a central host provides enterprise-grade security, scalability,

data integrity, performance, and application monitoring.

The following sections and chapters delve deeper into the features introduced here.

Entity Queries and Entity Navigation

Entity queries and entity navigation are the two mechanisms for retrieving business objects from a data

source. Both deposit business objects into the EntityManager’s cache.

You use entity queries to get started in a work flow. In response to a question like “What orders were

placed last month?”, they return Order entities. If your query asks “Which employees were hired last

year?”, you get Employee entities.

The results of entity queries are root objects. Once you have a root object, your subsequent queries are

often about entities related to the root object. Given employee “Sally”, you start exploring her object

graph by looking for her address, her manager, her orders, etc. You traverse Sally’s object graph using

entity navigation and it has its own simple and intuitive syntax.

Most applications require surprisingly few entity queries. Once you have a list of orders or employees

that interest you, you’re likely to settle in and poke around using entity navigation. It is common for

applications to show 10 or 20 times as many entity navigations as entity queries.

Since we can’t navigate anywhere until we have some root entities in hand, let’s start with entity

queries.

Page 127: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

127 | P a g e

Entity Queries Use an EntityQuery when you want to retrieve a set of business objects that satisfy selection criteria -

the set of employees who were hired last year, for example.

Entity queries come in many flavors. Some of them are linguistically independent of any particular data

source; some are specialized to a particular data source. Some can query the data source and the entity

cache at the same time; some can only query the data source12.

EntityQueries, like .NET ObjectQueries, are enumerable, and so can be executed in a variety of stepwise

ways. Consider, for example, the following query:

Code Snippet 1. BasicQueriesDemo.BasicQuerySyntaxQuery()

C#

var customersQuery =

from cust in _em1.Customers

where cust.ContactTitle == "Sales Representative"

orderby cust.CompanyName

select cust;

VB

Dim customersQuery =

From cust In _em1.Customers _

Where cust.ContactTitle = "Sales Representative" _

Order By cust.CompanyName _

Select cust

This can also be written in method-based syntax13 as

Code Snippet 2. BasicQueriesDemo.BasicMethodSyntaxQuery()

C#

var customersQuery = _em1.Customers

.Where(c => c.ContactTitle == "Sales Representative")

.OrderBy(c => c.CompanyName)

.Select(c => c);

VB

Dim customersQuery = _em1.Customers _

.Where(Function(c) c.ContactTitle = "Sales Representative") _

.OrderBy(Function(c) c.CompanyName) _

.Select(Function(c) c)

12 This means this kind of query can be used only when the application is connected to the data source; such queries can‟t run

when the application is off-line.

13 Query-based syntax looks a great deal like SQL and is, for that reason, attractive to many developers, especially those new to

LINQ. At IdeaBlade we tend to prefer the more regularly structured and comprehensive method-based syntax for most

queries, so you will see most of our sample queries in that format. Be assured, however, that you may write your LINQ

queries in the syntax you prefer!

We discuss the two syntaxes more in the section “Query v. Method Syntax”, in this document.

Page 128: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

128 | P a g e

Or just,

Code Snippet 3. BasicQueriesDemo.MethodSyntaxShortForm()

C#

var customersQuery = _Em1.Customers

.Where(c => c.ContactTitle == "Sales Representative")

.OrderBy(c => c.CompanyName);

VB

Dim customersQuery = _em1.Customers _

.Where(Function(c) c.ContactTitle = "Sales Representative") _

.OrderBy(Function(c) c.CompanyName)

Each of these returns an IdeaBlade.EntityQuery.EntityQuery<Customer>. If you choose to type your

variable to hold the query’s return value explicitly as a DevForce EntityQuery<T>, the statement

becomes the following:

Code Snippet 4. BasicQueriesDemo.QueryWithExplicitlyTypedReturnValue()

C#

IEntityQuery<Customer> customersQuery = _em1.Customers

.Where(c => c.ContactTitle == "Sales Representative")

.OrderBy(c => c.CompanyName);

VB

Dim customersQuery As IEntityQuery(Of Customer) = _

_em1.Customers _

.Where(Function(c) c.ContactTitle = "Sales Representative") _

.OrderBy(Function(c) c.CompanyName)

The following query retrieves only a single Customer entity is retrieved from the data source into the

local cache. If no Customer matches the stated criterion, DevForce returns the Null Entity Customer:

Code Snippet 5. BasicQueriesDemo.RetrieveFirstCustomer()

C#

Customer firstCustomer = _em1.Customers

.Where(c => c.ContactTitle == "Sales Representative")

.OrderBy(c => c.CompanyName)

.FirstOrNullEntity();

VB

Dim firstCustomer As Customer = _em1.Customers _

.Where(Function(c) c.ContactTitle = "Sales Representative") _

.OrderBy(Function(c) c.CompanyName) _

.FirstOrNullEntity()

The addition of a call to extension method ToList() forces DevForce to execute the query immediately:

Code Snippet 6. ForcingExecutionDemo.ForceImmediateExecution()

C#

ICollection<Customer> customersQuery = _em1.Customers

.Where(c => c.ContactTitle == "Sales Representative")

.OrderBy(c => c.CompanyName)

.ToList();

Page 129: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

129 | P a g e

VB

Private Sub ForceImmediateExecution()

Dim customersQuery As ICollection(Of Customer) = _

_em1.Customers _

.Where(Function(c) c.ContactTitle = "Sales Representative") _

.OrderBy(Function(c) c.CompanyName) _

.ToList()

The call to ToList(), because it demands a complete set of pointers to the retrieved matching customers,

forces the complete query to be executed. Below is a DevForce DebugLog listing for a test that first

issued a First() call like the one we just considered, then a call to ToList(). We’ve removed some of the

columns included in the log so the table won’t be quite so wide, but note the highlighted “Fetch …

value” messages. The first one, when delivered to the EntityFramework, will be translated into a SQL

query that returns a single record; the second will be translated into a SQL query that returns all of the

matching customers.

If you want to see the SQL generated by the EntityFramework to process your query, find the

appropriate edmKey in your App.Config file and add a logTraceString attribute set to “true”:

Page 130: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

130 | P a g e

This will result in output like the following. (Again, some columns were omitted to reduce the table

width for inclusion here.) Note the generated SQL statements:

In between the two extremes of asking a query object for its first element and asking it to dump its

contents ToList() are many possibilities, such as using it in a foreach loop:

Page 131: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

131 | P a g e

C#

IEntityQuery<Customer> customersQuery = _em1.Customers

.Where(c => c.ContactTitle == "Sales Representative")

.OrderBy(c => c.CompanyName);

foreach (Customer aCustomer in customersQuery) {

// All customers are retrieved at the start of the loop

}

VB

Dim customersQuery As IEntityQuery(Of Customer) = _em1.Customers.Where(Function(c)

c.ContactTitle = "Sales Representative").OrderBy(Function(c) c.CompanyName)

For Each aCustomer As Customer In customersQuery

' All customers are retrieved at the start of the loop

Next aCustomer

Code Snippet 7. ForcingImmediateExecution.ForceRetrievalUsingForEach()

The foreach loop returns references to the retrieved Customers one at a time, but it does so from a

collection of those references which must be obtained up front. Thus, as soon as the first iteration of the

loop is executed, the entire set of Customers is retrieved to the local cache, and a collection of

references to them is assembled. The debug log will show only a single query:

On the other hand, the following query results in exactly five (5) entities being retrieved from the data

source:

Code Snippet 8. SkipAndTakeDemo.QueryWithSkipAndTake()

C#

IEntityQuery<Customer> customersQuery = _em1.Customers

.Where(c => c.ContactTitle == "Sales Representative")

.OrderBy(c => c.CompanyName)

.With(QueryStrategy.DataSourceOnly);

ICollection<Customer> customers = customersQuery.Skip(5).Take(5).ToList();

VB

Dim customersQuery As IEntityQuery(Of Customer) = _em1.Customers _

.Where(Function(c) c.ContactTitle = "Sales Representative") _

.OrderBy(Function(c) c.CompanyName) _

.With(QueryStrategy.DataSourceOnly)

Dim customers As ICollection(Of Customer) = customersQuery.Skip(5).Take(5).ToList()

Note the use of the DataSourceOnly QueryStrategy. That’s often important when using Skip(). You can

learn why in the section of this chapter on FetchStrategies.

The With() Extension Method

DevForce provides an extension method, With(), that permits you to substitute a different

QueryStrategy, a different target EntityManager, or both, on an existing query. The original query will be

left unaltered.

When a call to With() is chained to a query, the result may be either a new query or a reference to the

original query. Normally it will be a new query, but if the content of the With() call is such that the

Page 132: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

132 | P a g e

resultant query would be the same as the original one, a reference to the original query is returned

instead of a new query.

If you ever want to be sure that you get a new query, use the Clone() extension method instead of

With(). With() avoids the overhead of a Clone() when a copy is unnecessary.

You can pass null arguments to With(). When a query has a null EntityManager assigned, it uses the

DefaultManager. When a query has a null QueryStrategy, it uses the DefaultQueryStrategy of the

assigned (or default) EntityManager. See the code below for more detail on the possibilities.

Code Snippet 9. QueriesWithWITH Demo.QueriesWithWITH()

C#

IEntityQuery<Customer> query0 = _em1.Customers

.Where(c => c.CompanyName.ToLower().StartsWith("a"));

query0.QueryStrategy = QueryStrategy.DataSourceOnly;

// Use With() to run the existing query against a different EntityManager:

ServerModelNorthwindIBContext em2 = new ServerModelNorthwindIBContext();

List<Customer> customers = new List<Customer>(query0.With(em2));

// The next two examples use With() to run the query with a different QueryStrategy.

// The With() call in the right-hand side of the following statement

// specifies a query that is materially different from query0, in

// that it has a different QueryStrategy associated with it.

// Accordingly, the right-hand side of the statement will return

// a new query:

IEntityQuery<Customer> query1 = query0.With(QueryStrategy.CacheOnly);

// Because the content of the With() call in the right-hand side

// of the following statement doesn't result in a modification

// of query0, the right-hand side will return a reference to

// query0 rather than a new query.

IEntityQuery<Customer> query2 = query0.With(QueryStrategy.DataSourceOnly);

// If you want to be certain you get a new query, use Clone()

// rather than With():

EntityQuery<Customer> query3 = (EntityQuery<Customer>)query0.Clone();

query3.QueryStrategy = QueryStrategy.DataSourceOnly;

// Change both the QueryStrategy and the EntityManager

IEntityQuery<Customer> query4 = query0.With(em2, QueryStrategy.CacheOnly);

// You can pass null arguments to With(). When a query has a null EntityManager

// assigned, it uses the DefaultManager. When a query has a null QueryStrategy,

// it uses the DefaultQueryStrategy of the assigned (or default) EntityManager.

// Run the query against the default EntityManager, using its default QueryStrategy:

IEntityQuery<Customer> query5 = query0.With(null, null);

// When you pass a single null to With, you must cast it to the appropriate

// type so the compiler know's which single-parameter overload you mean to use:

// Run the query against the default EntityManager, using the base query's

// assigned QueryStrategy:

IEntityQuery<Customer> query6 = query0.With((ServerModelNorthwindIBContext)null);

// Run the query against the assigned EntityManager, using that EntityManager's

// default QueryStrategy:

IEntityQuery<Customer> query7 = query0.With((QueryStrategy)null);

Page 133: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

133 | P a g e

VB

Dim query0 As IEntityQuery(Of Customer) = _em1.Customers.Where(Function(c)

c.CompanyName.ToLower().StartsWith("a"))

query0.QueryStrategy = QueryStrategy.DataSourceOnly

' Use With() to run the existing query against a different EntityManager:

Dim em2 As New DomainModelEntityManager()

Dim customers As New List(Of Customer)(query0.With(em2))

' The next two examples use With() to run the query with a different QueryStrategy.

' The With() call in the right-hand side of the following statement

' specifies a query that is materially different from query0, in

' that it has a different QueryStrategy associated with it.

' Accordingly, the right-hand side of the statement will return

' a new query:

Dim query1 As IEntityQuery(Of Customer) = query0.With(QueryStrategy.CacheOnly)

' Because the content of the With() call in the right-hand side

' of the following statement doesn't result in a modification

' of query0, the right-hand side will return a reference to

' query0 rather than a new query.

Dim query2 As IEntityQuery(Of Customer) = query0.With(QueryStrategy.DataSourceOnly)

' If you want to be certain you get a new query, use Clone()

' rather than With():

Dim query3 As EntityQuery(Of Customer) = CType(query0.Clone(), EntityQuery(Of Customer))

query3.QueryStrategy = QueryStrategy.DataSourceOnly

' Change both the QueryStrategy and the EntityManager

Dim query4 As IEntityQuery(Of Customer) = query0.With(em2, QueryStrategy.CacheOnly)

' You can pass null arguments to With(). When a query has a null EntityManager,

' assigned, it uses the DefaultManager. When a query has a null QueryStrategy,

' it uses the DefaultQueryStrategy of the assigned (or default) EntityManager.

' Run the query against the default EntityManager, using its default QueryStrategy:

Dim query5 As IEntityQuery(Of Customer) = query0.With(Nothing, Nothing)

' When you pass a single null to With, you must cast it to the appropriate

' type so the compiler know's which single-parameter overload you mean to use:

' Run the query against the default EntityManager, using the base query's

' assigned QueryStrategy:

Dim query6 As IEntityQuery(Of Customer) = query0.With(CType(Nothing,

DomainModelEntityManager))

' Run the query against the assigned EntityManager, using that EntityManager's

' default QueryStrategy:

Dim query7 As IEntityQuery(Of Customer) = query0.With(CType(Nothing, QueryStrategy))

The FirstOrNullEntity() ExtensionMethod

LINQ to Entities provides First() and FirstOrDefault() extension methods on queries. First() returns the

first item in a collection meeting the query criteria; FirstOrDefault() returns that, or if no items meet the

criteria, the default value for the target type. For integer target types, FirstOrDefault() returns a zero;

for string types, it returns an empty string. For complex types or other types that have no default, it

returns a null.

DevForce adds a FirstOrNullEntity() extension method that can be used when you are querying for target

types that inherit from IdeaBlade.EntityModel.Entity. If no entity meets the specified criteria,

FirstOrNullEntity() returns the DevForce NullEntity for the target type. The NullEntity is a non-saveable,

Page 134: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

134 | P a g e

immutable, syntactically correct instance of an entity represents “nothing there” but will not trigger an

exception.

The ToQuery () ExtensionMethod

Every IdeaBlade.EntityModel.Entity has a ToQuery() extension method that returns an IEntityQuery<T>

where T is an Entity type. This IEntityQuery<T> specifies the Entity on which it was based using its

EntityAspect.EntityKey, and can be extended to perform various useful operations. Consider, for

example, the following statements:

Code Snippet 10. UsingToQuery.UsingToQueryPt01

C#

Customer aCustomer = _em1.Customers.FirstOrNullEntity();

var query = aCustomer.ToQuery<Customer>()

.Include(Customer.PathFor(c => c.Orders));

query.With(QueryStrategy.DataSourceOnly).ToList();

VB

Dim aCustomer As Customer = _em1.Customers.FirstOrNullEntity()

Dim query = aCustomer.ToQuery().Include(Customer.PathFor(Function(c) c.Orders))

query.With(QueryStrategy.DataSourceOnly).ToList()

Page 135: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

135 | P a g e

Here, from a Customer entity, we have created a query that will retrieve that same Customer. We have

then extended with a call to Include() it to create a span query that will also retrieve all of that

Customer’s associated Orders. We do not otherwise have so convenient a way to accomplish this goal.

The ToQuery() extension method is also provided on any IEnumerable<T> collection, when T is an Entity.

Thus you can turn an arbitrary list of Customers into a query that will return the same set of Customers.

The Where() clause on the resultant query will specify a series of OR’d key values. For example, consider

the following statements:

Code Snippet 11. UsingToQuery.UsingToQueryPt02

C#

List<Customer> customers = _em1.Customers

.Where(c => c.CompanyName.ToLower().StartsWith("a")).ToList();

var query2 = customers.ToQuery<Customer>();

VB

Dim customers As List(Of Customer) = _em1.Customers _

.Where(Function(c) c.CompanyName.ToLower().StartsWith("a")).ToList()

Dim query2 = customers.ToQuery()

Placing query2 in a watch window reports its value as the following:

{value(IdeaBlade.EntityModel.EntityQueryProxy`1[DomainModel.Customer]).W

here(t => ((((t.CustomerID = 785efa04-cbf2-4dd7-a7de-083ee17b6ad2) ||

(t.CustomerID = b61cf396-206f-41a6-9766-168b5cbb8edd)) || (t.CustomerID =

f214f516-d55d-4f98-a56d-7ed65fd79520)) || (t.CustomerID = 256d4372-baa7-

4937-9d87-d9a4e06146f8)))}

The first query evidently placed four Customers in the customers list; the query returned by ToQuery()

specifies those four by their (GUID) key values.

Other Query Types

In addition to the EntityQuery, DevForce provides the PassthruESQLQuery and StoredProcQuery

types for querying using Entity SQL and stored procedures, respectively. Like the EntityQuery, these

types implement DevForce’s IEntityQuery interface.

Code Snippet 12. EsqlAndStoredProcQueriesDemo.PassThruEsqlQuery

C#

PassthruEsqlQuery query = new PassthruEsqlQuery(typeof(Employee),

"SELECT VALUE e FROM Employees AS e Where e.EmployeeID < 10");

IEnumerable results = _em1.ExecuteQuery(query);

VB

Dim query As New PassthruEsqlQuery(GetType(Employee), _

"SELECT VALUE e FROM Employees AS e Where e.EmployeeID < 10")

Dim results As IEnumerable = _em1.ExecuteQuery(query)

Page 136: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

136 | P a g e

Code Snippet 13. EsqlAndStoredProcQueriesDemo.StoredProcQuery

C#

QueryParameter param01 = new QueryParameter("EmployeeID",1);

QueryParameter param02 = new QueryParameter("Year",1996);

StoredProcQuery query = new StoredProcQuery(typeof(Order));

query.Parameters.Add(param01);

query.Parameters.Add(param02);

// Note that a FunctionImport must be defined in the Entity Model

query.ProcedureName = "OrdersGetForEmployeeAndYear";

_em1.ExecuteQuery(query);

VB

Dim param01 As New QueryParameter("EmployeeID", 1)

Dim param02 As New QueryParameter("Year", 1996)

Dim query As New StoredProcQuery(GetType(Order))

query.Parameters.Add(param01)

query.Parameters.Add(param02)

' Note that a FunctionImport must be defined in the Entity Model

query.ProcedureName = "OrdersGetForEmployeeAndYear"

_em1.ExecuteQuery(query)

The Query Object return type

An entity query returns one and only one kind of thing. That kind of thing is always an entity type

declared in the business object model. The query developer must identify that entity type and ensure

that the substance of the query actually will return such entities.

Although the query returns only one kind of entity, it may populate the entity cache with other kinds of

entities. You‟ll see just how useful this can be when we discuss span queries and query inversion.

The Fetch and Merge

The EntityManager evaluates the query and searches for suitable entities either in the cache, in the data

source, or in both. Where it looks for entities and what it does with the ones it finds are determined by a

QueryStrategy object which we will cover in the “Caching” topic below.

Query v. Method Syntax The following LINQ query is written in the syntax known as “query syntax”, “query comprehension

syntax”, or just “comprehension syntax”:

Code Snippet 14. BasicQueriesDemo.BasicQuerySyntaxQuery (Repeated)

C#

var customersQuery =

from cust in _Em1.Customers

where cust.ContactTitle == "Sales Representative"

orderby cust.CompanyName

select cust;

VB

Dim customersQuery =

From cust In _em1.Customers _

Where cust.ContactTitle = "Sales Representative" _

Order By cust.CompanyName _

Select cust

Page 137: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

137 | P a g e

This can also be written in method-based syntax as

Code Snippet 15. BasicQueriesDemo.BasicMethodSyntaxQuery (Repeated)

C#

var customersQuery = _Em1.Customers

.Where(c => c.ContactTitle == "Sales Representative")

.OrderBy(c => c.CompanyName)

.Select(c => c);

VB

Dim customersQuery = _em1.Customers _

.Where(Function(c) c.ContactTitle = "Sales Representative") _

.OrderBy(Function(c) c.CompanyName) _

.Select(Function(c) c)

At IdeaBlade we mostly prefer the method-based syntax as a general rule. The capabilities available in

method-based syntax are substantially a superset of those available in query syntax, so when using

query syntax you may be forced into concatenating method-based clauses anyway to get what you

want, as in the following:

Code Snippet 16. BasicQueriesDemo.MixedQueryAndMethodSyntax

C#

ICollection<Customer> customers =

(from cust in _em1.Customers

orderby cust.CompanyName

select cust)

.ToList();

VB

Dim customers As ICollection(Of Customer) = _

(From cust In _em1.Customers _

Order By cust.CompanyName _

Select cust)

.ToList()

Having said that, there are a few things that are arguably a bit easier or more natural to do in query

syntax14, and of course there are simply personal preferences. So use what you like!

LINQ The typical data-oriented approach to retrieving objects relies upon a specialized query language such as

SQL. SQL is a powerful query language requiring considerable sophistication and experience to use

properly. But there are pitfalls to using SQL and several good reasons to prefer LINQ to SQL queries,

including:

Object orientation

Compile time checking

14 Joseph and Ben Albahari, in a fine discussion of LINQ, opine that query comprehension syntax “is much simpler for queries

that involve any of the following:

A let clause for introducing a new variable alongside the iteration variable

SelectMany, Join, or GroupJoin, followed by an outer iteration variable reference”

See their excellent book C#3.0 In a Nutshell, O‟Reilly Media Inc., 2007, p.285

Page 138: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

138 | P a g e

Query portability

Query manipulation

LINQ is a vast subject and is, for the most part, beyond the scope of this document. A web search on “LINQ” will

provide you with an abundance of excellent resources for learning about LINQ.

It suffices to say here that our implementation of LINQ -- LINQ to DevForce -- permits the same query to be used

against a local cache or a back-end datasource supported by Microsoft‟s LINQ to Entities. You can specify, by

means of a QueryStrategy property on the query object, just what you want its target data store or data stores to be;

or you can let DevForce apply sensible defaults which work well for the majority of cases.

The DevForce Predicate Builder

The time comes when you want to construct a LINQ “Where” clause programmatically. It should be

easy. It turns out to be more challenging … until you use the DevForce PredicateBuilder. (You will find

this class in the IdeaBlade.Linq namespace, in either the IdeaBlade.Linq or IdeaBlade.Linq.SL [for

Silverlight] assembly.)

Imagine a product search interface. The user can enter words in a “Name Search” text box. Your

program should find and display every product that contains any of the words entered by the user. You

don’t know how many words the user might enter. What do you do?

The solution would be easy if you knew the user would enter exactly one word.

Code Snippet 17. UsingThePredicateBuilderDemo.ProductsWithNamesThatContainSpecifiedString

C#

var word = "Sir";

var q = _em1.Products

.Where(p => p.ProductName.Contains(word));

var results = q.ToList();// returns 3 Northwind products

VB

Dim word = "Sir"

Dim q = _em1.Products _

.Where(Function(p) p.ProductName.Contains(word))

Dim results = q.ToList() ' returns 3 Northwind products

Of course you don’t know how many words the user will enter. You want to be prepared for more than

one so you write this too-simple helper method that returns an array of words from the text entered in

the text box:

Code Snippet 18. UsingThePredicateBuilderDemo.GetWords

C#

private IEnumerable<String> GetWords(string phrase) {

return phrase.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);

}

VB

Private Function GetWords(ByVal phrase As String) As IEnumerable(Of String)

Return phrase.Split(New Char() {" "c}, StringSplitOptions.RemoveEmptyEntries)

End Function

Page 139: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

139 | P a g e

Now all you have to do is replace the “Where” clause with a sequence of OR clauses. You’ll want to

construct it by iterating over the words. Go ahead and write it. We’ll wait...

Having trouble? I’ll give you the user’s input: “Sir Cajun Louisiana”. Did that help?

You will probably come up with something like the following:

C#

var q = _em1.Products

.Where(p =>

p.ProductName.Contains("Sir") ||

p.ProductName.Contains("Cajun") ||

p.ProductName.Contains("Louisiana")

);

var results = q.ToList(); // returns 6 Northwind products

VB

Dim q = _em1.Products.Where(Function(p) _

p.ProductName.Contains("Sir") _

OrElse p.ProductName.Contains("Cajun") _

OrElse p.ProductName.Contains("Louisiana"))

Dim results = q.ToList() ' returns 6 Northwind products

Code Snippet 19. UsingThePredicateBuilderDemo.ProductsWithNamesThatContainSpecifiedStrings

This is ultimately what the lambda expression must look like.

Of course you cannot demand that the user enter exactly three words any more than you can insist she

enter exactly one. You want to construct the lambda dynamically based on the actual number of words

entered. Sadly, there is no obvious way of constructing a lambda expression dynamically.

But the DevForce PredicateBuilder can help you build predicates dynamically.

What‟s a “predicate”?

A “predicate” is a function that evaluates an expression and returns true or false.

The code fragment...

C#

/VB

p.ProductName.Contains(“Sir”)

...is a predicate that examines a product and returns true if the product’s ProductName contains the

“Sir” string.

The CLR type of the predicate in our example is:

Page 140: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

140 | P a g e

C#

Func<Product, bool>

VB

Func(Of Product, Boolean)

Which we can generalize to:

C#

Func<T, bool>

VB

Func(Of T, Boolean)

We almost have what we want. When the compiler sees an example of this kind of thing, it immediately

resolves it into an anonymous delegate. But we don’t want the delegate. We need a representation that

retains our intent and postpones the resolution into a delegate until the last possible moment; because

before we get that delegate, we may want to build a more complex expression. What we need is an

expression made up of Func<T, bool>’s:

C#

Expression<Func<T, bool>>

VB

Expression(Of Func(Of T, Boolean))

As it so happens, this is exactly what the DevForce “Where” extension method demands:

C#

public static IEntityQuery<T> Where<TSource>(

this IEntityQuery<T> source1, Expression<Func<T,bool>> predicate)

VB

public static IEntityQuery(Of T) Where(Of TSource) _

(Me IEntityQuery(Of T) source1, Expression(Of Func(Of T,Boolean)) predicate)

The methods of the static IdeaBlade.Linq.PredicateBuilder class take things even a step farther: they

permit us to combine two or more Predicate Expressions into a single Predicate Expression that we can

pass to that Where() method.

Let’s stick with the example and see one of those PredicateBuilder methods in action. Let’s first write a

little method to produce an IEnumerable of Predicate Expressions, one expression for each string in a

collection of strings:

Page 141: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

141 | P a g e

Code Snippet 20. UsingThePredicateBuilderDemo.ProductNameTests

C#

private IEnumerable<Expression<Func<Product, bool>>>

ProductNameTests(IEnumerable<String> words) {

foreach (var each in words) {

var word = each;

yield return p => p.ProductName.Contains(word);

}

}

VB

Private Function ProductNameTests(ByVal words As IEnumerable(Of String)) _

As IEnumerable(Of Expression(Of Func(Of Product, Boolean)))

Dim expressions As New List(Of Expression(Of Func(Of Product, Boolean)))()

For Each [each] In words

Dim word = [each] ' include this statement so *each* is evaluated at each iteration

expressions.Add(Function(p) p.ProductName.Contains(word))

Next [each]

Return expressions

End Function

The result is an IEnumerable of Predicate Expressions about the Product entity. The body is an iterator

that returns a Predicate Expression for each word. That expression is exactly the same as the first

predicate we wrote when we knew only one word.

If we give it the three-word input in our example, we’ll get an IEnumerable of three Predicate

Expressions, each looking for one of the words in the product’s ProductName.

We’re want to OR these Predicate Expressions together so we will use a static method of

PredicateBuilder named, well, Or():

C#

public static Expression<Func<T, bool>> Or<T>(

params Expression<Func<T, bool>>[] expressions)

VB

public static Expression(Of Func(Of T, Boolean)) Or(Of T) _

(params Expression(Of Func(Of T, Boolean))() expressions)

You see it takes an array (a params array to be precise) of Predicate Expressions. We will convert the

output of our ProductNameTests into an array before giving it to this PredicateBuilder method. The final

code looks like this:

Code Snippet 21. UsingThePredicateBuilderDemo.PredicateBuilder01

C#

var words = GetWords("Sir Cajun Louisiana");

var tests = ProductNameTests(words).ToArray();

if (0 == tests.Length) return;

var productNamePredicate = PredicateBuilder.Or(tests);

var q = _em1.Products.Where(productNamePredicate);

var results = q.ToList(); // returns 6 Northwind products

Page 142: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

142 | P a g e

VB

Dim words = GetWords("Sir Cajun Louisiana")

Dim tests = ProductNameTests(words).ToArray()

If 0 = tests.Length Then

Return

End If

Dim productNamePredicate = PredicateBuilder.Or(tests)

Dim q = _em1.Products.Where(productNamePredicate)

Dim results = q.ToList() ' returns 6 Northwind products

To summarize the steps we’re taking:

1. Split the user‟s search text into separate words

2. Generate an array of Predicate Expressions that look for each word in the ProductName

3. Skip the query if there are no clauses … because there are no words

4. Ask “PredicateBuilder.Or” to combine the tests into a single Predicate Expression

5. Run it to get results.

PredicateBuilder Methods

There are seven methods of interest:

Method Syntax by example

Or p1.Or(p2)

Or PredicateBuilder.Or(p1, p2, p3 .. pn)

And p1.And(p2)

And PredicateBuilder.And(p1, p2, p3 .. pn)

True PredicateBuilder.True()

False PredicateBuilder.False()

Not PredicateBuilder.Not(p1)

“p” = Predicate Expression, Expression<Func<T, bool>>.

All expressions must be of the same type (e.g., Product).

Examples

Here are some examples using the PredicateBuilder methods:

Code Snippet 22. UsingThePredicateBuilderDemo.PredicateBuilderMiscExamples

C#

Expression<Func<Product, bool>> p1, p2, p3, p4, bigP;

// Sample predicate expressions

p1 = p => p.ProductName.Contains("Sir");

p2 = p => p.ProductName.Contains("Cajun");

Page 143: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

143 | P a g e

p3 = p => p.ProductName.Contains("Louisiana");

p4 = p => p.UnitPrice > 20;

bigP = p1.Or(p2); // Name contains "Sir" or "Cajun"

bigP = p1.Or(p2).Or(p3); // Name contains any of the three

bigP = PredicateBuilder.Or(p1, p2, p3); // Name contains any of the 3

bigP = PredicateBuilder.Or(tests); // OR together some tests

bigP = p1.And(p4); // "Sir" and price is greater than 20

// Name contains "Cajun" and "Lousiana" and the price is greater than 20

bigP = PredicateBuilder.And(p2, p3, p4);

bigP = PredicateBuilder.And(tests); // AND together some tests

// Name contains either “Sir” or “Louisiana” AND price is greater than 20

bigP = p1.Or(p3).And(p4); //

bigP = PredicateBuilder.Not(p1); // Name does not contain "Sir"

bigP = PredicateBuilder.True<Product>().And(p1);// same as p1

bigP = PredicateBuilder.False<Product>().Or(p1);// same as p1

// Not useful

bigP = PredicateBuilder.True<Product>().Or(p1);// always true

bigP = PredicateBuilder.False<Product>().And(p1);// always false

VB

Dim p1 As Expression(Of Func(Of Product, Boolean)), p2 As Expression(Of Func(Of Product,

Boolean)), p3 As Expression(Of Func(Of Product, Boolean)), p4 As Expression(Of Func(Of

Product, Boolean)), bigP As Expression(Of Func(Of Product, Boolean))

' Sample predicate expressions

p1 = Function(p) p.ProductName.Contains("Sir")

p2 = Function(p) p.ProductName.Contains("Cajun")

p3 = Function(p) p.ProductName.Contains("Louisiana")

p4 = Function(p) p.UnitPrice > 20

bigP = p1.Or(p2) ' Name contains "Sir" or "Cajun"

bigP = p1.Or(p2).Or(p3) ' Name contains any of the three

bigP = PredicateBuilder.Or(p1, p2, p3) ' Name contains any of the 3

bigP = PredicateBuilder.Or(tests) ' OR together some tests

bigP = p1.And(p4) ' "Sir" and price is greater than 20

' Name contains "Cajun" and "Lousiana" and the price is greater than 20

bigP = PredicateBuilder.And(p2, p3, p4)

bigP = PredicateBuilder.And(tests) ' AND together some tests

' Name contains either "Sir" or "Louisiana" AND price is greater than 20

bigP = p1.Or(p3).And(p4)

bigP = PredicateBuilder.Not(p1) ' Name does not contain "Sir"

bigP = PredicateBuilder.True(Of Product)().And(p1) ' same as p1

bigP = PredicateBuilder.False(Of Product)().Or(p1) ' same as p1

' Not useful

bigP = PredicateBuilder.True(Of Product)().Or(p1) ' always true

bigP = PredicateBuilder.False(Of Product)().And(p1) ' always false

Page 144: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

144 | P a g e

Observations Regarding the PredicateBuilder Methods

Notice that one each of the Or(), And(), and Not() methods are Predicate Expression extension methods;

they make it easier to compose predicates from a number of Predicate Expressions known at design

time.

Put a breakpoint on any of the “bigP” lines and ask the debugger to show you the result as a string. Here

is the Immediate Window output for “bigP = p1.Or(p3).And(p4);”:

{p => ((p.ProductName.Contains("Sir") || p.ProductName.Contains("Louisiana")) &&

(p.UnitPrice > Convert(20)))}

The True() and False() methods return Predicate Expression constants that simply help you jumpstart

your chaining of PredicateBuilder expressions. Two of the combinations – True()…Or() and

False()…And() -- are not useful.

Example: Simulate an In() Clause Condition on a Distantly Related Entity

Consider the following query:

Code Snippet 23. UsingThePredicateBuilderDemo.PredicateBuilderForInClause()

C#

var employeeTerritoriesQuery = _em1.EmployeeTerritories

.Where(et => et.Employee.Orders.Any(o =>

o.Customer.City == "Albuquerque" ||

o.Customer.City == "Frankfurt" ||

o.Customer.City == "London" ||

o.Customer.City == "Rio de Janeiro" ||

o.Customer.City == "Sao Paulo"));

VB

Dim employeeTerritoriesQuery = _em1.EmployeeTerritories _

.Where(Function(et) et.Employee.Orders _

.Any(Function(o) o.Customer.City = "Albuquerque" OrElse _

o.Customer.City = "Frankfurt" OrElse _

o.Customer.City = "London" OrElse _

o.Customer.City = "Rio de Janeiro" OrElse _

o.Customer.City = "Sao Paulo"))

We have, in essence, placed an In() condition on the Customer for any Order associated with the

Employee that is associated with the EmployeeTerritory entities we want to retrieve. Of course, In() isn’t

support by the version of LINQ in .NET 3.5, so we had to code it the hard way.

Still, it works, so we’re happy until we realize that we need to use such a query in a situation where we

don’t know until runtime what cities – or how many cities – our end user will want to match. We need to

let that user pick the cities from a list, or even type their names in freeform.

For this, we’ll need the PredicateBuilder, as shown in the version of the query below. This version uses a

string array of city names as input to the query. We stuff that array in a code statement here, but it

could, of course, be populated by user input in the user interface.

Page 145: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

145 | P a g e

Code Snippet 24. UsingThePredicateBuilderDemo.PredicateBuilderForInClause()

C#

string[] targetCities = _

{ "Albuquerque", "Frankfurt", "London", "Rio de Janeiro", "Sao Paulo" };

IEnumerable<Expression<Func<EmployeeTerritory, bool>>> tests =

CustomerCityNameTests(targetCities.ToArray());

var cityNamePredicate = PredicateBuilder.Or(tests.ToArray());

IEntityQuery<EmployeeTerritory> search3 =

_em1.EmployeeTerritories.Where(cityNamePredicate);

search3.ToList();

private IEnumerable<Expression<Func<EmployeeTerritory, bool>>>

CustomerCityNameTests(IEnumerable<String> words) {

foreach (var each in words) {

var word = each; // must include this statement so *each* is evaluated at each iteration

yield return et => et.Employee.Orders.Any(o => o.Customer.City == word);

}

}

VB

Dim targetCities() As String = _

{"Albuquerque", "Frankfurt", "London", "Rio de Janeiro", "Sao Paulo"}

Dim tests As IEnumerable(Of Expression(Of Func(Of EmployeeTerritory, Boolean))) = _

CustomerCityNameTests(targetCities.ToArray())

Dim cityNamePredicate = PredicateBuilder.Or(tests.ToArray())

Dim search3 As IEntityQuery(Of EmployeeTerritory) = _

_em1.EmployeeTerritories.Where(cityNamePredicate)

search3.ToList()

Private Function CustomerCityNameTests(ByVal words As IEnumerable(Of String)) _

As IEnumerable(Of Expression(Of Func(Of EmployeeTerritory, Boolean)))

Dim predicateExpressions = _

New List(Of Expression(Of Func(Of EmployeeTerritory, Boolean)))()

For Each [each] In words

Dim word = [each] ' must include this statement so *each* is evaluated at each iteration

predicateExpressions.Add( _

Function(et) et.Employee.Orders.Any(Function(o) o.Customer.City Is word))

Next [each]

Return predicateExpressions

End Function

The PredicateBuilder versions retrieves exactly the same set of entities into the cache as the hard-coded

version.

The PredicateDescription Class

So far, so good: but what about when you need to build a filter for a query dynamically? For example,

suppose the filter criteria, including the search field and operator, are user-controlled (e.g., obtained

from UI controls). With the facilities you’ve seen so far, you don’t have a good tool.

Enter the PredicateDescription. Here are some examples:

Create two filters.

The snippet below comprises two statements, each of which uses PredicateBuilder.Make(Type type, string

propertyName, FilterOperator filterOp, object value) to create a PredicateDescription representing a single

predicate (filter criteria):

Code Snippet 25. UsingThePredicateBuilderDemo.PredicateDescriptions01

C#

Page 146: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

146 | P a g e

PredicateDescription p1 = PredicateBuilder.Make(typeof(Product), "UnitPrice",

FilterOperator.IsGreaterThanOrEqualTo, 24);

PredicateDescription p2 = PredicateBuilder.Make(typeof(Product), "Discontinued",

FilterOperator.IsEqualTo, true);

VB

Dim p1 As PredicateDescription = PredicateBuilder.Make(GetType(Product), "UnitPrice", _

FilterOperator.IsGreaterThanOrEqualTo, 24)

Dim p2 As PredicateDescription = PredicateBuilder.Make(GetType(Product), "Discontinued", _

FilterOperator.IsEqualTo, True)

Create a filter query, ANDing the two filters.

This snippet uses PredicateBuilder.FilterQuery(IQueryable baseQuery, IPredicateDescription

predicateDescription) to create a new, filtered query from a base query. The new query can then be executed

by an EntityManager as usual:

Code Snippet 26. UsingThePredicateBuilderDemo.PredicateDescriptions01

C#

var query = PredicateBuilder.FilterQuery(_em1.Products, p1.And(p2));

var results = _em1.ExecuteQuery<Product>((IEntityQuery<Product>)query);

// The above query is the same as:

//var queryb = _em1.Products.Where(p => p.UnitPrice > 24 && p.Discontinued);

VB

Dim query = PredicateBuilder.FilterQuery(_em1.Products, p1.And(p2))

Dim results = _em1.ExecuteQuery(Of Product)(CType(query, IEntityQuery(Of Product)))

' The above query is the same as:

'var queryb = _em1.Products.Where(p => p.UnitPrice > 24 && p.Discontinued);

Now let’s accomplish the same thing in a slightly different manner. Multiple predicates can be And’ed

and Or’ed together to form a CompositePredicateDescription.

Create a composite filter from two individual filters.

C#

PredicateDescription p1 = new PredicateDescription(typeof(Product),

"UnitPrice", FilterOperator.IsGreaterThanOrEqualTo, 24);

PredicateDescription p2 = new PredicateDescription(typeof(Product),

"Discontinued", FilterOperator.IsEqualTo, true);

// And the two filters.

CompositePredicateDescription p3 = p1.And(p2);

VB

Dim p1 As New PredicateDescription(GetType(Product), _

"UnitPrice", FilterOperator.IsGreaterThanOrEqualTo, 24)

Dim p2 As New PredicateDescription(GetType(Product), _

"Discontinued", FilterOperator.IsEqualTo, True)

' And the two filters.

Dim p3 As CompositePredicateDescription = p1.And(p2)

Code Snippet 27. UsingThePredicateBuilderDemo.CompositePredicateDescription

Page 147: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

147 | P a g e

We can‟t use the CompositePredicateDescription directly in or on a query. Instead we, must first convert it into

a Lambda expression. We can then use that in the query:

Create a lambda expression, and use that in a Where clause.

Code Snippet 28. UsingThePredicateBuilderDemo.CompositePredicateDescriptionToLambda

C#

using System.Linq.Expressions;

...

var exprFunc = (Expression<Func<Product, bool>>)p3.ToLambdaExpression();

var filterQuery = _em1.Products.Where(exprFunc);

var results = _em1.ExecuteQuery(filterQuery);

VB

Imports System.Linq.Expressions

...

Private exprFunc = CType(p3.ToLambdaExpression(), Expression(Of Func(Of Product, Boolean)))

Private filterQuery = _em1.Products.Where(exprFunc)

Private results = _em1.ExecuteQuery(filterQuery)

A PredicateDescription can always be instantiated from its constructor, and AND’d or OR’d with another

PredicateDescription to form a CompositePredicateDescription. The method ToLambdaExpression() can

be used to turn any predicate description into an expression which can be used in a standard LINQ

Where clause.

Example: Given a Collection of Parent Entities, Retrieve the Related Children

As a further example of the use of the PredicateBuilder and PredicateDescription types, let’s consider

the following scenario: you want to retrieve a set of Orders related to an arbitrary collection of

Customers. In fact, you’re going to let your end user select the Customers whose Orders she wants to

see. You won’t know until runtime.

Here’s the code:

Code Snippet 29. UsingThePredicateBuilderDemo.GetRelatedChildrenOfParentCollection

C#

// Start with a list of customers that you‟ve populated however you see fit,

// perhaps from end-user input. Here, we‟ll arbitrarily populate one as follows:

List<Customer> customers = _em1.Customers.Where(c => c.Country == "USA").ToList();

customers.AddRange(_em1.Customers.Where(c => c.Country == "Brazil").ToList());

// From the list of customers, create an IEnumerable<PredicateDescription>

var predicates = customers.Select(c => new PredicateDescription(typeof(Customer),

"CustomerId", FilterOperator.IsEqualTo, c.CustomerID));

// Convert that IEnumerable<PredicateDescription> to an array, and feed the array

// to the PredicateBuilder‟s Or() method to get a CompositePredicateDescription

var customerPredicate = PredicateBuilder.Or(predicates.ToArray());

// Convert the CompositePredicateDescription to a LambdaExpression; pass the

// LambdaExpression to the Where clause of a Customer query; and project out

// the related Orders using a SelectMany() call. Execute the query to retrieve

Page 148: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

148 | P a g e

// the desired Orders!

var exprFunc = (Expression<Func<Customer, bool>>)customerPredicate.ToLambdaExpression();

var ordersQuery = _em1.Customers.Where(exprFunc).SelectMany(c => c.Orders);

var orders = _em1.ExecuteQuery(ordersQuery);

VB

' Start with a list of customers that you‟ve populated however you see fit,

' perhaps from end-user input. Here, we‟ll arbitrarily populate one as follows:

Dim customers As List(Of Customer) = _

_em1.Customers.Where(Function(c) c.Country = "USA").ToList()

customers.AddRange(_em1.Customers.Where(Function(c) c.Country = "Brazil").ToList())

' From the list of customers, create an IEnumerable(Of PredicateDescription)

Dim predicates = customers.Select(Function(c) New PredicateDescription(GetType(Customer),

"CustomerId", FilterOperator.IsEqualTo, c.CustomerID))

' Convert that Ienumerable(Of PredicateDescription) to an array, and feed the array

' to the PredicateBuilder‟s Or() method to get a CompositePredicateDescription

Dim customerPredicate = PredicateBuilder.Or(predicates.ToArray())

' Convert the CompositePredicateDescription to a LambdaExpression; pass the

' LambdaExpression to the Where clause of a Customer query; and project out

' the related Orders using a SelectMany() call. Execute the query to retrieve

' the desired Orders!

Dim exprFunc = CType(customerPredicate.ToLambdaExpression(), _

Expression(Of Func(Of Customer, Boolean)))

Dim ordersQuery = _em1.Customers.Where(exprFunc).SelectMany(Function(c) c.Orders)

Dim orders = _em1.ExecuteQuery(ordersQuery)

PassthruESQL Queries

DevForce supports queries in Entity SQL (ESQL) with its PassThruEsqlQuery() method.

Code Snippet 30. EsqlAndStoredProcQueriesDemo.EsqlBasic

C#

var query = new PassthruEsqlQuery(typeof(Customer),

"SELECT VALUE c FROM Customers AS c Where c.Country == 'Brazil'");

var result = query.With(_em1).Execute().Cast<Customer>();

VB

Dim query = New PassthruEsqlQuery(GetType(Customer), _

"SELECT VALUE c FROM Customers AS c Where c.Country == 'Brazil'")

Dim result = query.With(_em1).Execute().Cast(Of Customer)()

As you can see, PassThruEsqlQuery() requires the Entity type to which you want references returned

and the ESQL query string.

Here’s an ESQL query that takes a parameter, “bonus”, which we’ll give a value of 2000:

Code Snippet 31. EsqlWithParameter

C#

var param = new QueryParameter("country", "Brazil");

var paramEsql = new ParameterizedEsql(

"SELECT VALUE c FROM Customers AS c Where c.Country > @country", param);

var query = new PassthruEsqlQuery(typeof(Customer), paramEsql);

var result1 = query.With(_em1).Execute().Cast<Customer>();

Console.WriteLine("Retrieved {0} Customers from {1}",

result1.Count(), param.Value.ToString()); // Retrieved 75 Customers from Brazil

param.Value = "Germany";

var result2 = query.With(_em1).Execute().Cast<Customer>();

Console.WriteLine("Retrieved {0} Customers from {1}",

result2.Count(), param.Value.ToString()); // Retrieved 46 Customers from Germany

Page 149: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

149 | P a g e

VB

Dim param = New QueryParameter("country", "Brazil")

Dim paramEsql = New ParameterizedEsql( _

"SELECT VALUE c FROM Customers AS c Where c.Country > @country", param)

Dim query = New PassthruEsqlQuery(GetType(Customer), paramEsql)

Dim result1 = query.With(_em1).Execute().Cast(Of Customer)()

Console.WriteLine("Retrieved {0} Customers from {1}", result1.Count(), _

param.Value.ToString()) „Retrieved 75 Customers from Brazil

param.Value = "Germany"

Dim result2 = query.With(_em1).Execute().Cast(Of Customer)()

Console.WriteLine("Retrieved {0} Customers from {1}", result2.Count(), _

param.Value.ToString()) „Retrieved 46 Customers from Germany

Note that the value of the parameter can be changed and the same query re-executed, returning

different results.

When you use Entity SQL, you’re responsible for formulating a query string that constitutes a valid

query. If you goof, you won’t know until you run it.

A PassthruEsqlQuery will not interrogate the local cache15. It goes directly to the Entity Data Model to

which the application must be connected when the query is issued.

The EntityServer will throw an exception if it cannot convert the result set into objects of the target

entity’s type. We highly recommend a try/catch around your passthru query call.

Remote Service Method Call (RSMC)

DevForce offers a Remote Service Method Call (RSMC) facility that enables a client-side caller to invoke

an arbitrary static method of a class accessible to the DevForce Business Object Server (BOS). The

method can return any kind of serializable object16: a list, a custom object, a list of custom objects, etc.

The client calls EntityManager.InvokeServerMethod() with the appropriate arguments: typically a

class name, method name, and arguments for the method.

An EntityServer instance in the BOS runs a security check and (if passed) invokes the requested method.

The BOS serializes the result and transmits it back to the requesting EntityManager which presents the

object to the caller after deserialization. It is up to the caller to make sense of this object.

There is no restriction on what the remote method does or how it does it. The object returned must be

serializable and – like business objects – must be of the same type on both client and server.

The RSMC mechanism ensures that remote method callers go through the same security checks as the

other EntityManager query methods.

15 We can extend some Passthru queries to search the cache. See “Advanced Business Object Concepts.”

16 RPC is not an “entity query” facility because it is not required to return entities.

Page 150: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

150 | P a g e

An asynchronous version of the Remote Service Method Call is also provided. It‟s perfect for any time-

consuming, server-based operation whose results are not needed immediately for continued work in the

client application. The asynchronously RSMC can, for example, be used to load huge and even unrelated

collections of data from the backend data store to the local cache without freezing the UI. The end user

continues productive work while the data is being loaded; and then subsequently enjoys extremely crisp

response in all aspects of the client application that depend upon the data that was loaded, which is now

available directly from the local cache.

Entity Navigation Entity navigation is a convenient syntax for accessing data from related business objects. Consider these

familiar scenarios:

Get all of a particular sales rep‟s orders.

Find the employee‟s home address

Calculate the sales tax for an order

In each instance, we want information (orders, address, sales tax table for the ship-to-address) related

to a single entity (salesrep, employee, order). The desired information exists somewhere in the entity’s

business object graph – the network of other entities that are related to our primary entity.

In DevForce, you can begin with an entity – arbitrarily designated the “root entity” – and traverse its

relations to reach other entities, both near and far. We call this “navigating the graph.”

All you do is write a simple navigation property expression such as myOrder.Customer.

Observe that the navigation property syntax, myOrder.Customer, looks just like one of the entity’s

simple properties, myOrder.ShippedDate. The key difference is that it returns an entity (Customer)

rather than a value (DateTime).

Entities have properties so you can write myOrder.Customer.Name. They have navigation properties so

you can walk further along the graph to the HeadquartersAddress entity where you’ll find the

headquarters city:

myOrder.Customer.HeadquartersAddress.City

Parent-Child Navigation properties

So far we’ve considered only navigation properties that return a single entity. Navigation properties can

return many entities. The myOrder.OrderDetails navigation property, for example, returns the many

line items of a single order.

Navigation properties that return multiple entities are invariable parent-child properties. The property

belongs to the parent entity such as Order and it returns child entities such as OrderDetail entities.

The navigation property returns child entities in a RelatedEntityList<T> collection. The

Order.OrderDetails property returns its OrderDetail children in a concrete collection,

RelatedEntityList<OrderDetail>.

Page 151: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

151 | P a g e

A brief example

I am writing a program in C#.

I write and run the following statements and learn that there are three line items in the collection

owned by anOrder:

Code Snippet 32. NavigationSynchronousDemo.NavigationSynchronousBasic

C#

Order anOrder = _em1.Orders.FirstOrNullEntity();

List<OrderDetail> lineItems = new List<OrderDetail>(anOrder.OrderDetails);

VB

Dim anOrder As Order = _em1.Orders.FirstOrNullEntity()

Dim lineItems As New List(Of OrderDetail)(anOrder.OrderDetails)

We decide to increase the quantity ordered for the first OrderDetail as follows.

C#

OrderDetail firstItem = lineItems[0];

firstItem.Quantity = 10;

VB

Dim firstItem As OrderDetail = lineItems(0)

firstItem.Quantity = 10

Navigation Properties in Silverlight

Because all data retrieval and save operations in Silverlight are required to be asynchronous, navigation

properties return their results to callback methods. Consider the following code:

Page 152: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

152 | P a g e

Code Snippet 33. NavigationBasicAsynchronous

C#

public void NavigationBasicAsynchronous() {

_em1.UseAsyncNavigation = true;

IEntityQuery<Order> query = _em1.Orders.Where(o => o.OrderID == 10248);

_em1.ExecuteQueryAsync<Order>(query, GotOrder, null);

PromptToContinue();

}

private void GotOrder(EntityQueryOperation<Order> args) {

if (args.Error != null) {

Console.WriteLine(args.Error.Message);

}

else {

// Retrieve a single related entity using a scalar navigation property

Order targetOrder = (Order)args.Results.ToList()[0];

Console.WriteLine("Order: {0}", targetOrder.OrderID.ToString());

targetOrder.Customer.PendingEntityResolved +=

new EventHandler<PendingEntityResolvedEventArgs>(

Customer_PendingEntityResolved);

Customer aCustomer = targetOrder.Customer;

Console.WriteLine("Customer (from GotOrders): {0}",

aCustomer.CompanyName);

// Retrieve a collection of related entities using a collection navigation property

targetOrder.OrderDetails.PendingEntityListResolved +=

new EventHandler<PendingEntityListResolvedEventArgs<OrderDetail>>(

OrderDetails_PendingEntityListResolved);

}

}

void Customer_PendingEntityResolved(object sender,

PendingEntityResolvedEventArgs e) {

Customer customer = (Customer)e.ResolvedEntity;

Console.WriteLine("Customer (from Customer_PendingEntityResolved): {0}",

customer.CompanyName);

}

void OrderDetails_PendingEntityListResolved(object sender,

PendingEntityListResolvedEventArgs<OrderDetail> e) {

Console.WriteLine("OrderDetails retrieved: {0}", e.ResolvedEntities.Count);

}

private void PromptToContinue() {

Console.WriteLine();

Console.WriteLine("Press ENTER to continue...");

Console.ReadLine();

}

Page 153: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

153 | P a g e

VB

Public Sub NavigationBasicAsynchronous()

ResetEntityManager(_em1)

_em1.UseAsyncNavigation = True

Dim query As IEntityQuery(Of Order) = _em1.Orders.Where(Function(o) o.OrderID = 10248)

_em1.ExecuteQueryAsync(Of Order)(query, AddressOf GotOrder, Nothing)

PromptToContinue()

End Sub

Private Sub GotOrder(ByVal args As EntityFetchedEventArgs(Of Order))

If args.Error IsNot Nothing Then

Console.WriteLine(args.Error.Message)

Else

' Retrieve a single related entity using a scalar navigation property

Dim targetOrder As Order = CType(args.Result.ToList()(0), Order)

Console.WriteLine("Order: {0}", targetOrder.OrderID.ToString())

AddHandler targetOrder.Customer.PendingEntityResolved, _

AddressOf Customer_PendingEntityResolved

Dim aCustomer As Customer = targetOrder.Customer

Console.WriteLine("Customer (from GotOrders): {0}", aCustomer.CompanyName)

' Retrieve a collection of related entities using a collection navigation property

AddHandler targetOrder.OrderDetails.PendingEntityListResolved, _

AddressOf OrderDetails_PendingEntityListResolved

'Console.WriteLine("OrderDetails retrieved: {0}",

targetOrder.OrderDetails.ToList().Count)

End If

End Sub

Private Sub Customer_PendingEntityResolved(ByVal sender As Object, ByVal e As

PendingEntityResolvedEventArgs)

Dim customer As Customer = CType(e.ResolvedEntity, Customer)

Console.WriteLine("Customer (from Customer_PendingEntityResolved): {0}",

customer.CompanyName)

End Sub

Private Sub OrderDetails_PendingEntityListResolved(ByVal sender As Object, ByVal e As

PendingEntityListResolvedEventArgs(Of OrderDetail))

Console.WriteLine("OrderDetails retrieved: {0}", e.ResolvedEntities.Count)

End Sub

Private Sub ResetEntityManager(ByVal em As EntityManager)

em.Clear()

em.UseAsyncNavigation = False

End Sub

In the method’s first statement we set the UseAsyncNavigation property of the EntityManager to true.

This step would be unnecessary in a Silverlight application, as true is the default setting for that property

in that environment. But the above code could run in both Silverlight and non-Silverlight environments.

Now consider the statements that retrieve the Order. For a couple of reasons, we can’t simply say this…

C#

Order anOrder = _em1.Orders.FirstOrNullEntity();

VB

Dim anOrder As Order = _em1.Orders.FirstOrNullEntity()

…firstly, because the attempt to execute the above statement would fail in a Silverlight app with a

message to the effect that “Queries in Silverlight must be executed asynchronously.” But in fact it also is

Page 154: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

154 | P a g e

not possible at present to execute asynchronously immediate execution queries (of which any query

ending with a call to FirstOrNullEntity() is an example). So to get our single Order, we need to submit a

query with a condition that retrieves the desired Order, as you saw in the main snippet. That query

must, of course, also be submitted asynchronously, and a callback method provided to process the

results.

C#

...

IEntityQuery<Order> query = _em1.Orders.Where(o => o.OrderID == 10248);

_em1.ExecuteQueryAsync<Order>(query, GotOrders, null);

...

}

private void GotOrder(EntityQueryOperation<Order> args) {

if (args.Error != null) {

Console.WriteLine(args.Error.Message);

}

else {

// Retrieve a single related entity using a scalar navigation property

Order targetOrder = (Order)args.Results.ToList()[0];

Console.WriteLine("Order: {0}", targetOrder.OrderID.ToString());

}

}

VB

...

Private IEntityQuery(Of Order) query = _em1.Orders.Where(Function(o) o.OrderID = 10248)

_em1.ExecuteQueryAsync(Of Order)(query, GotOrders, Nothing)

...

private void GotOrder(EntityFetchedEventArgs(Of Order) args)

If args.Error IsNot Nothing Then

Console.WriteLine(args.Error.Message)

Else

' Retrieve a single related entity using a scalar navigation property

Dim targetOrder As Order = CType(args.Result.ToList()(0), Order)

Console.WriteLine("Order: {0}", targetOrder.OrderID.ToString())

...

End If

End Sub

In this case, since we’re using the primary key to fetch our Order, we know that args.Result will contain

at most one entity; so we simply cast it into an Order and proceed.

To get the Customer related to that Order (refer back to the full snippet), we set up a handler for the

PendingEntityResolved event of the Customer navigation property, targetOrder.Customer. Then to

initiate the asynchronous retrieval of that customer, we reference it in a code statement:

C#

Customer aCustomer = targetOrder.Customer;

VB

Dim aCustomer As Customer = targetOrder.Customer

Page 155: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

155 | P a g e

We included a call to Console.WriteLine() immediately following the above statement just to show that

the desired Customer simply isn’t going to be available at that point. The statement will write out a

blank for the Customer’s CompanyName. Where we will get results is in the

Customer_PendingEntityResolved handler:

C#

void Customer_PendingEntityResolved(object sender,

PendingEntityResolvedEventArgs e) {

Customer customer = (Customer)e.ResolvedEntity;

Console.WriteLine("Customer (from Customer_PendingEntityResolved): {0}",

customer.CompanyName);

}

VB

Private Sub Customer_PendingEntityResolved(ByVal sender As Object, _

ByVal e As PendingEntityResolvedEventArgs)

Dim customer As Customer = CType(e.ResolvedEntity, Customer)

Console.WriteLine("Customer (from Customer_PendingEntityResolved): {0}", _

customer.CompanyName)

End Sub

Collection Navigation Properties in Silverlight

For navigation properties that return a collection, DevForce provides a PendingEntityListResolved event,

similar to the PendingEntityResolved event we’ve just discussed:

C#

private void GotOrder(EntityQueryOperation<Order> args) {

...

// Retrieve a collection of related entities using a collection navigation property

targetOrder.OrderDetails.PendingEntityListResolved +=

new EventHandler<PendingEntityListResolvedEventArgs<OrderDetail>>(

OrderDetails_PendingEntityListResolved);

}

}

void OrderDetails_PendingEntityListResolved(object sender,

PendingEntityListResolvedEventArgs<OrderDetail> e) {

Console.WriteLine("OrderDetails retrieved: {0}", e.ResolvedEntities.Count);

}

VB

Private Sub GotOrder(ByVal args As EntityQueryOperation(Of Order))

...

' Retrieve a collection of related entities using a collection navigation property

AddHandler targetOrder.OrderDetails.PendingEntityListResolved, _

AddressOf OrderDetails_PendingEntityListResolved

End If

End Sub

When we run the full snippet, the code displays the following results in the Console window:

Page 156: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

156 | P a g e

The output line “Press ENTER to continue..” comes from the utility method PromptToContinue(), which

executes synchronously and immedately. Then we see reflected back the OrderID of the retrieved

Order; the non-existent CompanyName of the not-yet-retrieved, related Customer; the CompanyName

of the Customer written after its retrieval by the Customer_PendingEntityResolved callback method; and

the display of OrderDetails retrieved, written by the OrderDetails_PendingEntityListResolved method.

Using An Anonymous Method for Navigation Property Callback

If you’re working in C#, you can also use inline, anonymous methods for your ExecuteQueryAsync()

callbacks:

Code Snippet 34. NavigationBasicAsynchronousAnonymousCallback (C# only)

C#

public void NavigationBasicAsynchronousAnonymousCallback() {

_em1.UseAsyncNavigation = true;

IEntityQuery<Order> query = _em1.Orders.Where(o => o.OrderID == 10248);

_em1.ExecuteQueryAsync<Order>(

query, // IEntityQuery<Order>

(args) => { // AsyncCompletedCallback

Console.WriteLine("Order: {0}", // "

((Order)args.Result.ToList()[0]).OrderID); // "

}, // "

null // UserState object

);

PromptToContinue();

}

These are handy when the logic to be included in the callback isn’t too involved. VB.NET doesn't support

multi-statement lambda expressions or anonymous methods.

Deferred Retrieval

When does the EntityManager fetch myOrder’s line items from the data source?

We might have written DevForce to fetch them automatically when it fetched myOrder. But if DevForce

were to get the line items automatically, why stop there? It could get the customer for the order, the

sales rep for the order, and the products for each line item.

Those are just the immediate neighbors. It could get the customer’s headquarter address, the sales rep’s

address and manager, and each product’s manufacturer. If it continued like this, it might fetch most of

the database.

Page 157: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

157 | P a g e

Retrieving the entire graph is obviously wasteful and infeasible. How often do we want to know the

manager of the sales rep who booked the order? Clearly we have to prune the object graph. But where

do we prune? How can we know in advance which entities we will need and which we can safely

exclude?

We cannot know. Fortunately, we don’t have to know17. We keep it simple. We use an entity query to

get the root entities (such as myOrder). Then we use entity navigation to retrieve neighboring related

entities as we need them.

This just-in-time approach is called deferred retrieval (also known as “lazy instantiation”, “lazy loading”,

“Just-In-Time *JIT+ data retrieval”, and so on).

Proactive Data Loads

Having established that the DevForce default is deferred retrieval, we hasten to add that there are many

circumstances when it absolutely makes sense to load data before it is specifically needed to satisfy

some demand of the application. Filling a large data grid is an excellent example of such a situation.

Suppose you’re filling a grid with Orders – lots of them – and that for each Order you also wish to display

the name of the Customer who placed it, the Sales Representative who wrote it, and the Shipping

Company that will deliver it. With deferred retrieval, filling a single row of the grid would require three

extra trips to the data source – one each for a Customer, Employee, and Shipper entity -- above and

beyond the one that got all of the Orders to begin with. If the grid were populated with a thousand

Orders, there would be three thousand separate (and unnecessary) trips to the data source to retrieve

the related entities. You can well imagine that this might negatively impact your application’s

performance.

For circumstances like these where there is an obvious impending need for a great deal of related data,

you can add Include() clauses to your data retrieval query to bring back the related data at the same

time your retrieve the root data. The following example retrieves selected Customers and a graph of

related data: the Customers’ Orders, the OrderDetails for those Orders, the Products referenced in the

OrderDetails, the Suppliers of those Products, and the SalesRep who wrote the Orders:

Code Snippet 35. NavigationSynchronousPreload

C#

IEntityQuery<Customer> query = _em1.Customers.Where(c => c.Country == "France")

.Include("Orders")

.Include("Orders.OrderDetails")

.Include("Orders.OrderDetails.Product")

.Include("Orders.OrderDetails.Product.Supplier")

.Include("Orders.SalesRep");

_em1.ExecuteQuery<Customer>(query);

// _em1.ExecuteQuery(query); // accomplishes the same thing

// query.ToList(); // accomplishes the same thing

17 We don‟t have to know if we can be certain of continuous connection to the data source. If we expect the application to run

offline, we‟ll have to anticipate the related entities we‟ll need and pre-fetch them. We‟ll get to this issue later.

Page 158: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

158 | P a g e

VB

Dim query As IEntityQuery(Of Customer) = _

_em1.Customers.Where(Function(c) c.Country = "France") _

.Include("Orders") _

.Include("Orders.OrderDetails") _

.Include("Orders.OrderDetails.Product") _

.Include("Orders.OrderDetails.Product.Supplier") _

.Include("Orders.SalesRep")

_em1.ExecuteQuery(Of Customer)(query)

' _em1.ExecuteQuery(query) // accomplishes the same thing

' query.ToList() // accomplishes the same thing

Proactive Data Loads in Silverlight

In Silverlight apps, where all data retrieval must be asynchronous, the benefits of preloading data are

even more general. In the following snippet, we preload, using a span query, a large object graph for

each of a group of Customers who meet a specified condition. Having done so, all of our subsequent

queries for entities can be cache-only and synchronous:

Code Snippet 36. NavigationAsynchronousPreload

C#

public void NavigationAsynchronousPreload() {

ResetEntityManager(_em1);

_em1.UseAsyncNavigation = true;

IEntityQuery<Customer> query = _em1.Customers.Where(c => c.Country == "France")

.Include("Orders")

.Include("Orders.OrderDetails")

.Include("Orders.OrderDetails.Product")

.Include("Orders.OrderDetails.Product.Supplier")

.Include("Orders.SalesRep");

_em1.ExecuteQueryAsync<Customer>(query, GotCustomers, null);

PromptToContinue();

}

private void GotCustomers(EntityFetchedEventArgs<Customer> args) {

if (args.Error != null) {

Console.WriteLine(args.Error.Message);

}

else {

DisplayCacheContents();

}

}

private void DisplayCacheContents() {

Console.WriteLine("Contents of Cache");

Console.WriteLine("-----------------");

Console.WriteLine("Customers: {0}", _em1.Customers.With(QueryStrategy.CacheOnly).Count());

Console.WriteLine("Employees: {0}", _em1.Employees.With(QueryStrategy.CacheOnly).Count());

Console.WriteLine("Orders: {0}", _em1.Orders.With(QueryStrategy.CacheOnly).Count());

Console.WriteLine("OrderDetails: {0}",

_em1.OrderDetails.With(QueryStrategy.CacheOnly).Count());

Console.WriteLine("Products: {0}", _em1.Products.With(QueryStrategy.CacheOnly).Count());

Console.WriteLine("Suppliers: {0}", _em1.Suppliers.With(QueryStrategy.CacheOnly).Count());

}

VB

Public Sub NavigationAsynchronousPreload()

ResetEntityManager(_em1)

Page 159: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

159 | P a g e

_em1.UseAsyncNavigation = True

Dim query As IEntityQuery(Of Customer) = _em1.Customers _

.Where(Function(c) c.Country = "France") _

.Include("Orders") _

.Include("Orders.OrderDetails") _

.Include("Orders.OrderDetails.Product") _

.Include("Orders.OrderDetails.Product.Supplier") _

.Include("Orders.SalesRep")

_em1.ExecuteQueryAsync(Of Customer)(query, AddressOf GotCustomers, Nothing)

PromptToContinue()

End Sub

Private Sub GotCustomers(ByVal args As EntityFetchedEventArgs(Of Customer))

If args.Error IsNot Nothing Then

Console.WriteLine(args.Error.Message)

Else

DisplayCacheContents()

End If

End Sub

Private Sub DisplayCacheContents()

Console.WriteLine("Contents of Cache")

Console.WriteLine("-----------------")

Console.WriteLine("Customers: {0}", _

_em1.Customers.With(QueryStrategy.CacheOnly).Count())

Console.WriteLine("Employees: {0}", _

_em1.Employees.With(QueryStrategy.CacheOnly).Count())

Console.WriteLine("Orders: {0}", _

_em1.Orders.With(QueryStrategy.CacheOnly).Count())

Console.WriteLine("OrderDetails: {0}", _

_em1.OrderDetails.With(QueryStrategy.CacheOnly).Count())

Console.WriteLine("Products: {0}", _

_em1.Products.With(QueryStrategy.CacheOnly).Count())

Console.WriteLine("Suppliers: {0}", _

_em1.Suppliers.With(QueryStrategy.CacheOnly).Count())

End Sub

Here is the output of the above method:

Missing objects

Every order should have a shipping address. What if it doesn’t? Will myOrder.ShippingAddress.City

throw an exception? Will we have to wrap every entity navigation in a giant try/catch block?

Will it return null? Will we have to follow every entity navigation with a test for null? That might be

worse than catching an exception.

Page 160: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

160 | P a g e

Fortunately entity navigation neither returns a null nor throws an exception. Instead, when the

EntityManager discovers there is no shipping address, it returns the Address Null Entity.

The Null Entity The null entity is a sentinel object that looks and behaves, for the most part, like a real entity instance.

Every entity class defines its own “null entity” instance.

When a query such as anEntityManager.DiscontinuedProducts must return an entity and it has no

valid entity instance to return, it returns a null entity of the requested type instead. When a navigation

property should return a related entity instance and there is no such instance, it will return a null entity

instead.

This is far better than returning a null (Nothing in VB). The caller can’t do a thing with null and may

even crash.

The null entity, on the other hand, has the properties of a real entity instance. For example, it can report

its type and the EntityManager that owns it18. All cached entities answer to IsNullEntity; only a null

entity replies true.

Most of its properties return runtime safe but semantically “empty” values that can be displayed in a UI.

If anEmployee is a null entity, for example, the expression anEmployee.FirstName returns an empty

string. The navigation property anEmployee.Orders returns an empty IList<Order>. The navigation

property anEmployee.HomeAddress returns the Address null entity.

This means we can write a long expression such as anEmployee.HomeAddress.State.Name without

throwing an exception. In this case the Address null entity’s State navigation property returns a State

null entity whose Name property returns an empty string.

The null entity cannot be changed, deleted, or saved. But the savvy developer can redefine a null

entity’s default property responses by overriding the UpdateNullEntity() method in the entity’s

Developer class19. She could change the Address.City property, for example, so that it returns the

string “<unknown>”.

Asynchronous Communication with the Business Object Server

The EntityManager supports asynchronous versions of methods which communicate with the BOS.

These methods include:

ConnectAsync

LoginAsync

LogoutAsync

ExecuteQueryAsync

18 Like real cached entities, null entities must belong to a EntityManager and, in fact, are created by a EntityManager

19 This method is inherited from the root business object class, Entity.

Page 161: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

161 | P a g e

ExecuteQueryAsync<T>

SaveChangesAsync

ForceIdFixupAsync

RefetchEntitiesAsync

InvokeServerMethodAsync

Asynchronous communication with the BOS is considerably more complicated than synchronous; but

alas, it is the law of the land in Silverlight applications. So, for those many of you who are working in that

environment, we are addressing the topic here, rather than in the Business Object Persistence –

Advanced document.

The EntityManager supports a hybrid of the .NET event-based asynchronous pattern20 for these

asynchronous methods. We refer to it as a “hybrid” because corresponding events have not been

defined for these methods. So instead of subscribing to an event to receive notification about the

completion status, you can instead pass a method-specific callback as part of the call. You can identify

this hybrid pattern by the OperationNameAsync naming convention.

See, for example, the code below to submit a query asynchonously.

Asynchronous Queries You’ve seen asynchronous queries earlier in this document, but here we revisit them with a slightly

more formal treatment, and in the context of other asynchronous communications with the Business

Object Server.

The following code defines an EntityQuery and launches it asynchronously, assigning the result set to a

list in the operation’s callback method:

Code Snippet 37. BOSCom_AsyncQuery

C#

private void BOSCom_AsyncQuery() {

ResetEntityManager(_em1);

var query = new EntityQuery<Customer>()

.Where(c => c.Country == "Denmark");

int token = 1;

_em1.ExecuteQueryAsync<Customer>(query,

QueryCompletedCallback, token);

}

private void QueryCompletedCallback(EntityQueryOperation<Customer> args) {

var resultList = args.Results;

Console.WriteLine("Query returned {0} entities", resultList.Count());

}

20 The standard .NET “Event-based Asynchronous Pattern” is described in described in an article at this

URL:

http://msdn2.microsoft.com/en-us/library/wewwczdw(en-US,VS.80).aspx

Page 162: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

162 | P a g e

VB

Private Sub BOSCom_AsyncQuery()

ResetEntityManager(_em1)

Dim query = New EntityQuery(Of Customer)().Where(Function(c) c.Country = "Denmark")

Dim token As Integer = 1

_em1.ExecuteQueryAsync(Of Customer)(query, AddressOf QueryCompletedCallback, token)

End Sub

Private Sub QueryCompletedCallback(ByVal args As EntityQueryOperation(Of Customer))

Dim resultList = args.Results

Console.WriteLine("Query returned {0} entities", resultList.Count())

PromptToContinue()

End Sub

As you’ve seen previously, in C#, you have the additional option of passing a lambda expression for the

callback instead of defining a separate method:

Code Snippet 38. BOSCom_AsyncQueryLambda

C#

private void BOSCom_AsyncQueryLambda() {

var query = new EntityQuery<Customer>().Where(c => c.Country == "Denmark");

int token = 2;

_em1.ExecuteQueryAsync<Customer>(

query,

(args) => {

var resultList = args.Results;

Console.WriteLine("Query returned {0} entities", resultList.Count());

},

token);

PromptToContinue();

}

The signature for the above queries is as follows:

C#

public EntityQueryOperation<T> ExecuteQueryAsync<T>(IEntityQuery<T> query,

Action<EntityQueryOperation<T>> userCallback, object userState = null);

VB

You can run multiple ExecuteQueryAsync operations simultaneously. The final parameter, userState, is a

unique object created by the developer to identify the async query. When a query completes, the

UserState is returned to the caller as part of the EntityQueryOperation argument so she can distinguish

one query from another. The UserState object can be as simple as an integer, or it can be an arbitrarily

complex custom type.

Completed Queries

The EntityQueryOperation parameter passed into an async query’s callback method contains the

following members:

Page 163: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

163 | P a g e

Property Description

Cancelled True if the query was canceled.

Cancellation of an async operation can be ordered by a call to

EntityQueryOperation.Cancel (). Such cancellation only succeeds if the order is

received in time.

ChangedEntities An IList containing every entity added to or modified in the EntityManager

cache.

Error An Exception object, of an exception was thrown during the async operation.

IsCompleted True if the operation completed successfully.

IsCompletedSynchronously True if the operation was executed synchronously and completed successfully. A

query, for example, will execute synchronously if DevForce determines that it

can be satisfied entirely from the EntityManager cache.

EntityQuery The IEntityQuery<T> object used in the asynchronous operation.

Results An IEnumerable<T> of returned objects.

UserState The object passed in the async call uniquely to identify the operation.

IAsyncResult Asynchronous Pattern For those needing additional control over their asynchronous operations, the EntityManager also

supports the IAsyncResult asynchronous pattern through an explicit implementation of the

IEntityManagerAsync interface. You will need to cast an EntityManager to this interface in order to use

methods following this pattern. In the IAsyncResult pattern an asynchronous operation is implemented

as two methods named BeginOperationName and EndOperationName to begin and end the

asynchronous operation "OperationName". More information on using this interface is available in the

IdeaBlade DevForce Reference Help, available from the IdeaBlade DevForce Windows Start menu.

Asynchronous Fulfillment of Navigation Property Queries DevForce returns data for navigation properties (such as Order.Customer or Order.OrderDetails) by

issuing queries. Explicit queries in your DevForce app can be written using the asynchronous method

calls detailed above, but control over the fulfillment of navigation properties must be exercised in a

different manner.

The EntityManager now has a boolean UseAsyncNavigation property that can be set to specify that

navigation properties should be fulfilled using asynchronous queries.

When reference is made to a navigation property, DevForce returns either an entity (if the property is

scalar) or a RelatedEntitiesList<T> (for collection properties). Entities now have an IsPendingEntity

property;

Page 164: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

164 | P a g e

When EntityManager.UseAsyncNavigation is set to true, the entities initially returned for scalar

properties, and the RelatedEntityLists returned for collection properties, will have a “pending” state

until the asynchronous query issued for their fulfillment actually returns data. This state can be

diagnosed with one of the following properties:

Entity.IsPendingEntity

RelatedEntityList<T>.IsPendingEntityList

Entities and RelatedEntityLists also now have events that fire when the data for pending entities is

returned. These are:

Entity.PendingEntityResolved RelatedEntityList<T>.PendingEntityListResolved

Handlers can be attached to these event to perform actions when the data for pending entities becomes

available to your app.

Canceling Pending Operations We may attempt to cancel an asynchronous operation by calling the Cancel() method on the operation

object returned by the asynchronous call.

The caller can confirm that the query was successfully canceled by checking the Cancelled parameter of

the EventArgs object; it should read true.

The EntityListManager

Instances of IdeaBlade.EntityModel.EntityListManager<T> watch the DevForce cache for changes and

add entity references to designated lists if such changes meet developer-defined rules.

Consider the following code:

Code Snippet 39. SetUpEntityListManager

C#

var filter = new Predicate<Employee>(

delegate(Employee anEmployee) { return anEmployee.City == "London"; });

_employeeEntityListManager =

new EntityListManager<Employee>(_em1, filter, null);

bool refreshListWhenPlacedUnderManagement = true;

_employeeEntityListManager.ManageList(_salesReps,

refreshListWhenPlacedUnderManagement);

VB

Dim filter = New Predicate(Of Employee)( _

Function(anEmployee As Employee) anEmployee.City = "London")

_employeeEntityListManager = New EntityListManager(Of Employee)(_em1, filter, Nothing)

Dim refreshListWhenPlacedUnderManagement As Boolean = True

_employeeEntityListManager.ManageList(_salesReps, refreshListWhenPlacedUnderManagement)

This code sets up an EntityListManager to watch the cache for changes to Employees, or the insertion of

new Employees. If any changed or new Employee is found to be based in London, a reference to that

Page 165: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

165 | P a g e

Employee will be added to the _salesReps list. At the same time, _employeeEntityListManager will

inspect all items in the _salesReps list to see that they meet the specified rule about London. The only

requirements for _salesReps are that it

implement System.Collections.IList; and

contain instances of IdeaBlade.EntityModel.Entity.

A single EntityListManager can manage as many different lists as you wish. To put

_employeeEntityListManager in charge of additional lists, you would simply invoke its ManageList()

method again for each desired list:

C#

_employeeEntityListManager.ManageList(_telecommuters, false);

_employeeEntityListManager.ManageList(_fieldAgents, false);

VB

_employeeEntityListManager.ManageList(_telecommuters, False)

_employeeEntityListManager.ManageList(_fieldAgents, False)

Of course, it only makes sense to do this when the same inclusion criteria apply to each targetted list.

In additions to changes to the cache, changes to a managed list trigger action by the managing

EntityListManager. Thus, any of the follows statements will cause _employeeEntityListManager to

examine the current contents of the cache and add references to all London employees to the

_salesReps list:

C#

_salesReps.Add(anEmployee);

_salesReps.Remove(anEmployee);

_salesReps.Clear();

VB

_salesReps.Add(anEmployee)

_salesReps.Remove(anEmployee)

_salesReps.Clear()

In the case of the statement _salesReps.Clear(), you will not end up with an empty list unless you first

remove _salesReps from the list of lists being managed by employeeEntityListManager. Removing an

entity that the rule says should be included also will not result in the entity disappearing from the list.

The EntityListManager will just put it right back! In general, beware of making manual changes (adds or

removals) to the set of items contained in a managed list.

EntityListManagers and The NullEntity

One exception occurs when you want the NullEntity for the type contained in a list to be included.

NullEntities are singletons and do not reside in the cache, so there is no way that an EntityListManager

Page 166: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

166 | P a g e

will ever find one there to add a reference to! If you want the NullEntity in a managed list, you should

manually add it. The ListManager will not remove it.

EntityListManagers and Duplicates

The EntityListManager will not eliminate duplicates from a list. For example, suppose you direct the

following statement against a list, _salesReps, that is already being managed to include Employees

based in London:

C#

_salesReps.ReplaceRange(_entityManager.Employees.Where(e=>e.City == "London"));

VB

_salesReps.ReplaceRange(_entityManager.Employees.Where(e=>e.City == "London"))

You will end up with duplicate references to each of the London employees!

EntityListManagers and Performance

EntityListManagers do create a certain amount of overhead, so be judicious in their use. It is also

possible to narrow their scope of what they must monitor more than we did in our examples above. We

instantiated our EntityListManager as follows:

C#

var filter = new Predicate<Employee>(

delegate(Employee anEmployee) { return anEmployee.City == "London"; });

_employeeEntityListManager =

new EntityListManager<Employee>(_entityManager, filter, null);

VB

The third argument, which we left null, is an array of EntityProperty objects. By leaving it null, we told

the manager to submit any added or modified Employee to the test encoded in the filter Predicate.

Suppose that, instead, we pass a list of properties of the Employee to this argument:

C#

new EntityListManager<Employee>(_entityManager, filter,

new EntityProperty[]{Employee.CityEntityProperty});

VB

_employeeEntityListManager = New EntityListManager(Of Employee)(_em1, filter, _

New EntityProperty() {Employee.CityEntityProperty})

Page 167: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

167 | P a g e

Now the EntityListManager will apply its test (about City being equal to London) only to an Employee

whose City property, specifically, was modified. If you simply change only the Birthdate of an Employee

already in the cache, the rule will not be evaluated. It can, after all, be safely assumed that said

Employee would already be in the lists being managed if the value in its City property were “London”.

Coding More Involved Rules

In the examples above we passed an anonymous delegate to the constructor of the Predicate filter.

That’s great for simple rules, but you can declare the predicate separately if you need to do something

more involved. This also gives you a chance to name the rule, which can make your code more readable.

Here’s a simple example:

Code Snippet 40. SetUpEntityListManagerWithNamedDelegate

C#

private void SetUpEntityListManagerWithNamedDelegate() {

// Identify Customer currently being edited by some process;

// this is a stand-in.

_currentCustomer = _em1.Customers.FirstOrNullEntity();

EntityListManager<Order> orderEntityListManager =

new EntityListManager<Order>(_em1, FilterOrdersByDate,

new EntityProperty[] {

Order.OrderDateEntityProperty,

Order.CustomerEntityProperty }

);

}

/// <summary>

/// This rule gets the 1996 Orders for the current Customer

/// </summary>

/// <param name="pOrder"></param>

/// <returns></returns>

Boolean FilterOrdersByDate(Order pOrder) {

return (pOrder.OrderDate.Value.Year == 1996 &&

pOrder.Customer == _currentCustomer);

}

VB

Private Sub SetUpEntityListManagerWithNamedDelegate()

' Identify Customer currently being edited by some process;

' this is a stand-in.

_currentCustomer = _em1.Customers.FirstOrNullEntity()

Dim orderEntityListManager As New EntityListManager(Of Order)(_em1, _

AddressOf FilterOrdersByDate, New EntityProperty() { _

Order.OrderDateEntityProperty, Order.CustomerEntityProperty})

End Sub

''' <summary>

''' This rule gets the 1996 Orders for the current Customer

''' </summary>

''' <param name="pOrder"></param>

''' <returns></returns>

Private Function FilterOrdersByDate(ByVal pOrder As Order) As Boolean

Return (pOrder.OrderDate.Value.Year = 1996 AndAlso

pOrder.Customer.Equals(_currentCustomer))

End Function

Page 168: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

168 | P a g e

Entity Caching

There are at least three good reasons to cache business objects:

1. The connection to the server may break during a session

2. Writing business object changes directly to the data source is impractical and often unwise.

3. In real life applications, the same entities are retrieved repeatedly; it wastes time and resources to bother the

server with redundant requests for the same entities.

Each DevForce EntityManager has its own, private entity cache that:

holds all retrieved and newly created entities;

is searchable by query and object navigation;

tracks cached entity changes, deletions and additions;

insulates the developer from cache mechanics;

enables the developer to control how entities are fetched and merged into the cache;

raises events when entities are fetched, changed, deleted, or added;

permits the developer to manipulate the cache when necessary;

can be persisted to and retrieved from client storage.

All Business Objects are Cached We always create or retrieve a business object into the cache of a particular Entity Manager instance.

Every business object can report to which Entity Manager instance it belongs. The developer can

rummage around in its cache discovering and manipulating the business objects therein.

Entity Ancestry and Organization of the Cache The business object developer class inherits from IdeaBlade.EntityModel.Entity.

Entity itself inherits from IdeaBlade.EntityModel.EntityWrapper, which implements several interfaces.

These include:

IEditableObject

IRevertibleChangeTracking

IChangeTracking

INotifyPropertyChanged

INotifyDataErrorInfo

IComparable

ICloneable

Page 169: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

169 | P a g e

Within the EntityManager cache, an Entity instance lives in an IdeaBlade.EntityModel.EntityGroup,

which lives within an EntityCache. You may find that you rarely need to interact directly with an

EntityGroup; and virtually all of the metadata you will ever need about an entity can be accessed

through the Entity’s EntityAspect.EntityMetaData property. Public properties and methods of that

include the following:

Member

Type

Name Function

Property EntityType Gets the Type of the entity

Property IsComplexType Returns whether this metadata describes a

"ComplexObject"

Property DataSourceKeyName Gets the data source key name.

Property DefaultEntitySetName The default EntitySetName for entities of this type.

Property EntityProperties

Returns a collection of EntityProperties that belong

to entities of this type.

Property DataProperties

Returns a collection of DataEntityProperties for

entities of this type.

Property NavigationProperties Returns a collection of DataEntityProperties for

entities of this type.

Property KeyProperties Returns a collection of EntityProperties that are keys

for entities of this type.

Property ConcurrencyProperties Returns a collection of EntityProperties that are

concurrency properties for entities of this type.

Property ComplexTypeProperties

Returns a collection of EntityProperties that describe

complex object properties for entities of this type.

Page 170: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

170 | P a g e

Property CanQueryByEntityKey

Gets whether primary key queries are allowed.

Method CreateEntity() Creates a new entity of the type describe by this

metadata item.

Method GetDefaultValue(Type

pType)

Returns the default value of a type: usually '0' or null

for any data type. Note that this is subtly different

from the TypeFns.GetDefaultValue method in that it

returns Today for a default date time.

Business objects are unique in each cache DevForce persistence management ensures that each business object appears at most once in a

particular EntityManager cache. No matter how many times the employee “Nancy Davolio” is read into

the cache, she appears at most once. Within the application, a reference to any “Nancy Davolio”

employee object is a reference to the same one employee object. If we change her first name to “Sue”,

she becomes “Sue” everywhere in the session unless …

… unless there is more than one EntityManager instance21. Each EntityManager instance maintains its

own independent cache. The “Nancy Davolio” retrieved into EM1 is not the same object as the “Nancy

Davolio” retrieved into EM2, even though they are both mapped to the same row in the Employee table

of the database.

Changes to a copy of a business object in one cache are invisible to other copies in other caches both in

this client and in all other clients. Changes become visible to other caches only after the object is saved

to the data source and re-fetched to those caches.

Entities in Lists Entities in lists are always references to entities in the EntityManager’s cache. This is true whether the

EM maintains the list or you maintain the list.

In general we prefer to work with only one list of entities of a particular type. But it may be useful to

have two such lists that are a little different.

For example, one list could hold all employees of the company while the second list holds the subset of

those employees who are managers. Both lists contain references to the same employee instances in

cache but they are very different lists.

21 Multiple EntityManagers have their place but most applications will need only one. Multiple EMs are covered in

“Advanced Business Object Concepts”.

Page 171: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

171 | P a g e

If we change the Employee ‘A’ who happens to be a manager, we are also changing the Employee ‘A’ in

the general employee list. They are the same Employee ‘A’.

If follows that if the PM re-fetches a clean copy of Employee 'A' from the data source, the pending

changes will disappear for all viewers of Employee ‘A’ whether they are looking at ‘A’ in the first list or in

the second list.

Business object proper, not the business object graph When speaking of a business object held in cache, we may easily lose sight of what we mean by a

“business object.”

We distinguished earlier between the “business object proper”, which encapsulates the simple, scalar

values stored in the object’s base table, and the “business object graph” which embraces the entire

network of other business objects to which it is related.

For example, the simple Employee properties such as “FirstName” and “LastName” access data values

that are stored in the Employee table; these are properties of the employee business object proper. The

“HomeAddress” navigation property, on the other hand, delivers a related business object, the

employee’s home address. The data values of the address come from a different table (Address) and

“belong” to the address business object proper, not the employee per se.

An EntityManager instance retrieves and holds business objects proper, not their graphs. Objects in the

graph of a particular business object may be in the cache. Or they may not. They don’t enter the cache

simply by virtue of being in another object’s graph.

The employee’s home address object will not enter the cache just because we retrieved the employee

object. It will enter the cache after we execute an expression such as anEmployee.HomeAddress.

Queries, Navigation, and the Cache We’ve covered entity queries and entity navigation. Although entity queries make explicit reference to

the EntityManager, we learned that entity navigation is also performed by the EntityManager.

Here we explain how the EntityManager processes both explicit entity queries and the implicit queries

inside entity navigation syntax.

We will see that EM query processing is guided by a query strategy. When following the default,

“normal” strategy, the EM tries first to satisfy a query from data in its cache; it reaches out to the data

source only if it must.

Query Cache

When a EntityManager begins to process a normal query, it checks its query cache to see if it has

processed this exact query before.

Page 172: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

172 | P a g e

The query cache holds queries and is not the same as the entity cache which holds objects and is what we

usually mean when we refer to “the cache.”

If the EntityManager finds the query in the query cache, it assumes that the objects which satisfy the

query are in the entity cache; accordingly, it satisfies the query entirely from the cache without

consulting the data source.

A one-to-many entity navigation, such as from employee to the employee’s orders, is translated

implicitly to an entity query language (OQL) query that also enters the query cache. The next time the

application navigates from that same employee to its orders, the EntityManager will recognize that it

has performed the query before and look only in the cache for those orders.

The query cache grows during the course of a session. Certain operations clear it as one of their side-

effects; removing an entity from the cache is one such operation. The developer can also clear the query

cache explicitly.

We just said that the EntityManager searches the query cache for an exact match of the current query,

but that was really a “little white first approximation.” Actually, the EntityManager does better than

that: it searches either for an exact match, or for an unrestricted query returning the same type. If, for

example, you have previously retrieved “all Customers” and now ask for “Customers from Canada”, your

new query will be satisfied from the cache.

Primary key queries

A query for business objects by primary key may be resolved entirely in the cache. If we search22 for the

employee with Id = ‘1’ the EntityManager will try to find it in the cache and, if not found there, will only

then look for it in the data source.

The EntityManager treats navigation along a one-to-one relationship, such as from Employee to

HomeAddress, as a primary key query. Navigation in the parent direction along a one-to-many

relationship, such as from an OrderDetail to its parent Order, is also a primary key query.

“Object Not Found” and the Null Entity

When we search for an entity and do not find it, the EntityManager, rather than returning a null that

may cause an exception in your application, returns a “sentinel” object called the Null Entity. Such a

sentinel behaves much like a real entity of the sought-for type except that it can’t be changed, deleted,

or saved. Every business object class defines its own null entity. See “The Null Entity” elsewhere in the

section on queries and navigation.

Cache use when disconnected

When the EntityManager “knows” it is disconnected from the server, it will satisfy a navigation, or a

query submitted with the Normal QueryStrategy, from the cache alone; it will not attempt to search the

22 If we use the default QueryStrategy; we are just about to discuss QueryStrategy so bear with me.

Page 173: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

173 | P a g e

data source. If a sought-for object is not in the cache, the EntityManager will return the Null Entity for

objects of that type.

The EntityManager raises an exception if it discovers during query processing that it can’t reach the

data source; see the “Lost Connections” topic in the “Advanced Business Object Concepts” section

below.

Modifications

Each business object carries a read-only EntityState property that indicates if the object is new,

modified, marked for deletion, or unchanged since it was last retrieved.

It bears repeating that our local modifications affect only the cached copy of a business object, not its

version in the data source. The data source version won’t be updated until the application tells the

EntityManager to save the changed object.

It follows that the data source version can differ from our cached copy either because we modified the

cached copy or because another user saved a different version to the data source after we retrieved our

copy.

It would be annoying at best if the EntityManager overwrote our local changes each time it queried the

data source. Fortunately, in a normal query, the EntityManager will only replace an unmodified version

of an object already in the cache; our modified objects are preserved until we save or undo them.

Stale Entity Data

All of this is convenient. But what if another user has made changes to a cached entity? The local

application is referencing the cached version and is unaware of the revisions. For the remainder of the

user session, the application will be using out-of-date data.

The developer must choose how to cope with this possibility. Delayed recognition of non-local changes

is often acceptable. A list of U.S. States or zip codes is unlikely to change during a user session. Employee

name changes may be too infrequent and generally harmless to worry about. In such circumstances the

default caching and query behavior is fine.

If concurrency checking is enabled and the user tries to save a changed object to the data source, DevForce

will detect the collision with the previously modified version in the data source. The update will fail and

DevForce will report this failure to the application which can take steps to resolve it.

Some objects are so volatile and critical that the application must be alert to external changes. The

developer can implement alternative approaches to maintaining entity currency by invoking optional

DevForce facilities for managing cached objects and forcing queries that go to the data source and

merge the results back into the cache.

The facilities for this are detailed in the section “Query Strategy” further on in this chapter.

Page 174: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

174 | P a g e

Fetch Life Cycle Events

DevForce raises the client-side Querying event as query execution begins. The Fetching event is raised

before sending the query to the EntityServer; note that if a query can be satisfied from cache then

Fetching is not raised. The Queried event is raised just before returning query results. We can listen to

these events by attaching a custom handler.

The Fetching event provides the query object. Our handler can examine the object (it implements

IEntityQuery) and choose to let the query through, modify it first, or cancel it. If we cancel the query,

the Entity Manager method returns as if it found nothing23.

The Queried event fires just before the query method returns. Entities have been fetched and merged

into the cache. The event arguments include the list of entities that came from the data source. There

might be none if the query found nothing or was satisfied entirely from the cache. It could include

entities of the target entity type – the kind we expected returned from the query. It could include

entities of other types as is likely if this is a span query or if the query provoked query inversion24.

Query Workflow Putting these points together, we can construct a schematic workflow for normal25 DevForce entity

queries and entity navigation when the application is connected to the Business Object Server (BOS)

running on its own physical tier.

Table 1. Entity Query and Navigation Workflow When QueryStrategy = Normal

Component Action

Client Tier – Application Code

The client application requests a particular

set of entities (the “desired entities”) either

by entity query or by entity navigation

Client Tier – EntityManager

Raises Fetching event. Listeners can see

the query and, optionally, cancel the query.

Checks if it can satisfy the query with the

entities in the client-side cache. If so, it

returns them immediately; end of

workflow.

If not, the EntityManager sends the

query along with authentication

information to the Business Object Server

(BOS) on the middle tier. It may modify

the request before sending to the BOS if it

can determine that some of desired entities

23 If the method returns a scalar entity, it yields the return entity type‟s Null Entity; otherwise, it returns a null entity list.

Beware of canceling an entity navigation list query method

24 Span queries are later in this section. We cover “Query Inversion” in the “Advanced Business Object Concepts”.

25 The workflow is different in a few places when we use a different QueryStrategy. See the “QueryStrategy” topic under

“Advanced Business Object Concepts”.

Page 175: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

175 | P a g e

are already in the client side cache.

Middle Tier - Business Object Server

The BOS authenticates the client (the

currently logged in “user”) and runs any

developer-specified security checks in the

AuthorizeQuery handler. If security

checks fail, it raises a security exception

and sends this back to the client tier.

Middle Tier - Business Object Server

Having passed security checks, the BOS

converts the query into one or more LINQ-

to-Entities queries in the form expected by

the ADO.NET Entity Framework. If a

relational database is the data source, the

Entity Framework converts the LINQ to

Entities query into one or more SQL

queries and submits them to the data source

query mechanism.

Data source – Data Source

The data source performs the query or

queries and returns one or more result sets

back to the Business Object Server.

Middle Tier - Business Object Server

The Entity Framework converts the result

sets returned from the data source into

ADO.NET entities and delivers them to the

EntityServer.

Middle Tier – Business Object Server

The EntityServer repackages the entities

obtained from the data source into a format

that can be transmitted efficiently. It then

ships the entity data to the client side

application.

Middle Tier – Business Object Server

After transmission, the BOS allows the

server‟s local copy of the entities to go out

of scope and the garbage collector reclaims

them. This enables the BOS to stay

stateless.

Client Tier –EntityManager

Client Tier –EntityManager: Compares

fetched entities to entities already in the

cache. Adds new entities to the cache.

Replaces matching cached entities that are

unmodified (in essence refreshing them).

Preserves cached entities with pending

modifications because the query strategy is

normal.

Client Tier –EntityManager: Reapplies the

original query to the cache to locate all

desired entities.

Client Tier –EntityManager: Raises the

Fetched event. Listeners can examine the

list of entities actually retrieved from the

data source.

Client Tier –EntityManager: Returns the

desired entities to the application.

Client Tier – Application Code

Client Tier – Application Code: The

entities are available for processing.

The application developer may proceed blissfully unaware of all this effort.

Page 176: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

176 | P a g e

Query Strategy When the EntityManager performs a query, it follows a query strategy. That strategy determines several

things, chief among them these:

the source of the data returned in a query;

how data obtained from a source external to the EntityManager cache is merged with existing

data in the cache; and

how issues related to satisfaction of the query from the cache are handled.

The QueryStrategy is a settable property of the query itself:

Code Snippet 41. QueryStrategyAssortedSyntaxExamples

C#

EntityQuery<Order> query01 = _em1.Orders;

query01.QueryStrategy = QueryStrategy.DataSourceThenCache;

VB

Dim query01 As EntityQuery(Of Order) = _em1.Orders

query01.QueryStrategy = QueryStrategy.DataSourceThenCache

In addition, every EntityManager has a DefaultQueryStrategy that is used whenever you do not explicitly

specify the query strategy you want to use with a particular query. You can also change this default:

C#

_em1.DefaultQueryStrategy = QueryStrategy.Normal;

VB

_em1.DefaultQueryStrategy = QueryStrategy.Normal

Entity navigation (e.g., myEmployee.Orders) is implemented with relation queries governed by the

DefaultQueryStrategy. In addition, any query whose QueryStrategy property has a value of null will

be executed with the DefaultQueryStrategy for the EntityManager underwhich it is run.

Page 177: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

177 | P a g e

The QueryStrategy object has four properties: FetchStrategy, MergeStrategy, InversionMode, and

TransactionSettings. The FetchStrategy controls where DevForce looks for the requested data: in the

cache, in the datasource, or in some combination of the two. The MergeStrategy controls how

DevForce resolves conflicts between the states of objects which, although already in the cache, are also

retrieved from an external source. The InversionMode controls whether DevForce attempts to retrieve

objects that are referenced in the query but are not the target type (e.g., the query “give me all

Customers with Orders in the current year” will return references to Customer objects, but must process

Order objects along the way). The TransactionSettings object permits you to control the TimeOut and

IsolationLevel associated with a query, and also whether and how to use the Microsoft Distributed

Transaction Coordinator.

There are five static (Shared in VB) properties in the IdeaBlade.EntityModel.QueryStrategy class

that return the five most common combinations of a FetchStrategy, a MergeStrategy, and an

InversionMode. These will be named and discussed momentarily, but are much easier to understand

after examining the available FetchStrategy, MergeStrategy, and InversionMode options.

Fetch Strategies

Five FetchStrategies are available in DevForce:

Table 2. FetchStrategies

Strategy Action

CacheOnly Apply this query against the cache only, returning references

only to entities already there. Do not consult the data source.

(Note that this query leaves the cache unchanged.) DataSourceOnly Retrieve matching entries from the datasource into the entity

cache. Return references only to those entities retrieved

from the the data source. A result set returned from a query

using this FetchStrategy would not include locally added

entities that had not yet been persisted to the data source. DataSourceThenCache First retrieve matching entries from the datasource into the

entity cache. Discard all references to entities retrieved in

this step.

Resubmit the same query against the updated cache. Return

references only to entities matched by this second,

CacheOnly query. DataSourceAndCache First retrieve matching entries from the datasource into the

entity cache. Retain references to entities retrieved in this

step.

Resubmit the same query as CacheOnly. Combine (union)

the references obtained in this second, CacheOnly query

with those obtained in the data source retrieval step. Optimized Check the query cache to see if the current query has

previously been submitted (and, if necessary, inverted)

successfully. If so, satisfy the query from the entity cache,

and skip the trip to the datasource.

Page 178: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

178 | P a g e

If the query cache contains no query matching or

encompassing the current query, then determine if all

entities needed to satisfy the query correctly from the cache

can be retrieved into the cache.26

If so, apply the

DataSourceThenCache FetchStrategy. Otherwise, apply the

DataSourceOnly FetchStrategy.

Operation of the FetchStrategies When the Client is Disconnected from the Data Source

If the client is disconnected from the data source, the DataSourceOnly, DataSourceThenCache, and

DataSourceAndCache strategies will throw an InvalidOperationException. The Optimized strategy will

behave as a CacheOnly query. It will not throw an exception, even if no matching query exists in the

query cache.

MergeStrategies

A MergeStrategy comes into play whenever DevForce discovers that an entity retrieved from an external

source already exists in the entity cache. (The two versions are recognized as the same entity because of

matching type and primary key value.) The MergeStrategy determines how DevForce will resolve any

conflict found in the two instances of the entity.27

DevForce supports five different MergeStrategies: PreserveChanges, OverwriteChanges,

PreserveChangesUnlessOriginalObsolete, PreserveChangesUpdateOriginal, and NotApplicable. Their

meanings are shown in Table 3.

When reviewing the table, remember that, for every cached DevForce entity, two states are maintained:

Original and Current. The Original state comprises the set of values for all properties as they existed at

the time of the last retrieval from, or save to, the datasource. The Current state comprises the set of

values for the object’s properties as the end user sees them. That is, the Current state values reflect any

local changes that have been made since the entity was retrieved, or last saved. When an entity is

persisted, it is the values in its Current state that are saved.

Table 3. MergeStrategies

Strategy Action when cached entity has pending changes

PreserveChanges Preserves the state of the cached entity.

OverwriteChanges Overwrites the cached entity with data from the data source. Sets the

EntityState of the cached entity to Unchanged.

PreserveChangesUnless OriginalObsolete

Preserves the values in the Current state of the cached entity, if its

Original state matches the state retrieved from the datasource.

If the state as retrieved from the datasource differs from that found

locally in the Original set of property values, this indicates that the

entity has been changed externally by another user or process. In this

case (with this MergeStrategy), DevForce overwrites the local entity,

26 See the discussion on query inversion for more detail.

27 Conflicts are diagnosed by comparing the values in the entity‟s designated Concurrency column.

Page 179: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

179 | P a g e

setting the values in both its Current and Original states to match that

found in the datasource. DevForce also then sets the EntityState of the

cached instance to Unchanged.

PreserveChangesUpdateOriginal Unconditionally preserves the values in the Current version for the

cached entity; and also updates the values in its Original version to

match the values in the instance retrieved from the datasource. This

has the effect of rendering the local entity savable (upon the next

attempt), when it might otherwise trigger a concurrency exception.

NotApplicable This merge strategy must be used – and may only be used – with the

CacheOnly fetch strategy. No merge action applies because no data is

retrieved from any source outside the cache.

We drill deeper into the topic of merge strategies in the section “MergeStrategy In More Detail” much

later in this chapter. We suggest you defer reading that at least until you’ve completed this section on

Query Strategy – so you don’t miss the big picture.

InversionMode

Query inversion applies to queries which:

a) are directed against a data source, and

b) though returning references to instances a single business object type, or a scalar simple type,

must process other types in order to acquire the result.

For example, the query “get me all Customers with Orders in the current year” will return references to

Customer objects, but must first examine many Order objects in order to return the correct set of

Customers. The query “give me the count of Customers located in Idaho” will return an integer, but

must examine the Customer collection in the data source.

Query inversion is the process of retrieving those non-targeted objects that are nonetheless necessary

for correct completion of a query. The most fundamental reason for doing query inversion is so that the

query can be applied against a pool of data that combines unpersisted local data with data that exists in

the datasource. This is, after all, what your end user normally wants: query results based on the state of

the data as she has modified it.

The only place that combined pool of data can exist, prior to persisting changes, is the local cache.

Therefore the query must ultimately be applied against the cache; and that operation, if it is to return

correct results, requires the cache to contain all entities that must be examined in the course of

satisfying the query. So to satisfy the query “get me all Customers with Orders in the current year”, the

cache must contain not only the Customers to which references will be returned, but also all extant

current-year Orders, so we can know which Customers those are.

A handy side-effect of inverting queries is that the same query, if resubmitted during the same

application session, can be satisfied entirely from the cache, without requiring another trip to the

datasource. Another results from the fact that there is a reasonably good statistical chance that the

Page 180: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

180 | P a g e

related objects needed for satisfaction of the query will also be referenced in other ways by the

application. In this very common scenario, the effect of the extra data retrieved is to improve client-side

performance by eliminating the need for separate retrieval of the related objects.

Note that the end result of a query inversion process is very similar to that which occurs when the

.Include() method is used in a query. Both processes result in the retrieval and local storage of objects

that are related to a set of root objects that are the primary target of a particular query.

Four InversionModes are available in DevForce for a query:

Table 4. InversionModes

Strategy Implicit Instuctions to DevForce

On Attempt to retrieve, from the datasource and into the cache, entities other than the

targetted type which are needed for correct processing of the query. If this

attempt fails, throw an exception. Off Do not attempt to retrieve entities other than the targetted type into the cache. Try Attempt to retrieve, from the datasource and into the cache, all entities other than

the targetted type which are needed for correct processing of the query. However,

if this attempt fails, just retrieve the entities of the directly targetted type, and do

not throw an exception. Manual Don‟t attempt to invert the current query; but act as if it were successfully

inverted (if it needed to be).

You (the developer) should only use this InversionMode when you are prepared to

guarantee, on your own, that the entity cache contains (or will contain, after the

DataSource portion of the query operation) all the necessary related objects to

return a correct result if submitted against the cache. Normally you would make

good on this guarantee by performing other data retrieval operations (prior to the

one in question) to retrieve the necessary related data; or by including calls to the

Include() extension method in the current query, sufficient to retrieve the

necessary related data.

The default InversionMode is Try, and this will likely be your choice for most queries.

You should use On only if your application absolutely depends upon the related entities being brought

into the cache by your query, and you should include exception handling in case the strategy fails.

Choose the Off setting if you only want the targeted entries retrieved into the cache. Be sure you choose

a compatible FetchStrategy.

For queries that DevForce can successfully invert, the InversionModes of Try and On will yield the same

end state: the query will be cached, and all related objects necessary to permit future satisfaction of the

query entirely from the cache will be assumed to be present in the cache. If you use the InversionMode

of Manual properly – that is, you take care to see that the necessary related objects get retrieved into

the cache by some means or another before the query is submitted – then it, too, will produce the same

ending state as the Try and On settings.

Page 181: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

181 | P a g e

Queries That Cannot Be Inverted

The following types of queries cannot be inverted:

A query that returns a scalar result. This includes all aggregate queries (Count, Sum, Avg, etc.).28

C#

var query02 = _em1.Orders.Select(o => o.FreightCost).Sum();

VB

Dim query02 = _em1.Orders.Select(Function(o) o.FreightCost).Sum()

A query whose return type is a single element. These include queries that call .First(), .Last(), and

.Single()

C#

var query03 = _em1.Products.OrderByDescending(c => c.ProductName).FirstOrNullEntity();

VB

Dim query03 = _em1.Products.OrderByDescending(Function(c) c.ProductName) _

.FirstOrNullEntity()

A query whose return type is different from the type contained in the collection first referenced.

C#

var query04 = _em1.Customers

.Where(c => c.Country == "Argentina")

.SelectMany(c => c.Orders);

VB

Dim query04 = _em1.Customers _

.Where(Function(c) c.Country = "Argentina") _

.SelectMany(Function(c) c.Orders)

” much later in this chapter. Again, we suggest you defer reading that at least until you’ve completed

this section on Query Strategy.

Pre-Defined QueryStrategies

As mentioned previously, every QueryStrategy combines a FetchStrategy, a MergeStrategy, and a

InversionMode. Since there are five FetchStrategies, five MergeStrategies, and four InversionModes,

28 Note that this group includes the example mentioned earlier in this discussion: “Give me the count of Customers located in

Idaho.”

Page 182: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

182 | P a g e

there are potentially 100 versions of QueryStrategy, even keeping the TransactionSettings constant.

However, in practice, a much smaller set of QueryStrategies suffices for the great majority of purposes.

DevForce has identified five of them as being of particular significance, enshrining them as static (Shared

in VB) properties of the QueryStrategy class. These pre-defined QueryStrategies combine FetchStrategy,

MergeStrategy, and InversionMode strategies as shown in Table 5.

Table 5. Fetch and merge strategies of the common query strategies

Query Strategy Fetch Strategy Merge Strategy InversionMode

Normal Optimized PreserveChanges Try

CacheOnly CacheOnly (Not Applicable) (Not Applicable)

DataSourceOnly DataSourceOnly OverwriteChanges Off

DataSourceThenCache DataSourceThenCache OverwriteChanges Try

DataSourceOnlyWithQueryInversion DataSourceAndCache OverwriteChanges On

Here’s how you assign a pre-defined QueryStrategy:

C#

query04.QueryStrategy = QueryStrategy.DataSourceThenCache;

VB

query04.QueryStrategy = QueryStrategy.DataSourceThenCache

Custom QueryStrategies

As just noted, only five of the possible combinations of a FetchStrategy and a MergeStrategy are

covered by the named QueryStrategies. What if you want one of the other combinations?

You can create your own QueryStrategy by supplying the fetch and merge strategy enumerations to its

constructor. The result is a new immutable QueryStrategy instance29.

29 Immutable meaning that we can get the component fetch and merge strategies but we cannot reset them.

Page 183: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

183 | P a g e

Here’s an example of the creation and assignment of a custom QueryStrategy:

C#

QueryStrategy aQueryStrategy =

new QueryStrategy(FetchStrategy.DataSourceThenCache,

MergeStrategy.PreserveChanges,

QueryInversionMode.On);

VB

' Creating a custom QueryStrategy

Dim aQueryStrategy As New QueryStrategy(FetchStrategy.DataSourceThenCache, _

MergeStrategy.PreserveChanges, _

QueryInversionMode.On)

DefaultQueryStrategy

We mentioned earlier that the DevForce EntityManager has a DefaultQueryStrategy property that can

be used to shape the fetch and merge behavior of queries where the QueryStrategy is not explicitly

specified. The default setting for the EntityManager’s DefaultQueryStrategy is QueryStrategy.Normal. If

you leave this setting at its default value, and in an individual query do nothing to countermand the

default settings, then the FetchStrategy of Optimized will be used in combination with the

MergeStrategy of PreserveChanges.

If for some reason you wanted a EntityManager where the default QueryStrategy would always involve

a trip to the data source, you could assign a different QueryStrategy, such as DataSourceOnly, to the

PM’s DefaultQueryStrategy property. For a given query, you could still use any desired QueryStrategy by

explicitly specifying a different one.

When to Use The Different QueryStrategies

For most users, most of the time, the DevForce defaults are perfect:

Satisfy a query from the entity cache whenever possible;

When a trip to the data source is found necessary, resolve any conflicts that occur between

incoming data and data already cache by giving the local version priority; and

Perform query inversion as needed; if needed and undoable, revert to a DataSourceOnly

FetchStrategy.

Your choice of a non-default strategy can be driven by a variety of things. For example, suppose your

application supports online concert ticket sales. Your sales clerks need absolutely up-to-date

information about what seats are available at the time they make a sale. In that use case, it will be

essential to direct your query for available seats against the data source, so a FetchStrategy of

DataSourceOnly might be in order.

Page 184: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

184 | P a g e

In code to handle concurrency conflicts, one might need a QueryStrategy with a MergeStrategy of

PreserveChangesUpdateOriginal to make an entity in conflict savable. (The data source version of the

conflicted entity would only be retrieved and used to partially overwrite the cache version after the

concurrency conflict had been resolved by some predetermined strategy.)

You can and will think of your own reasons to use different combinations of FetchStrategy,

MergeStrategy, and InversionMode. Just ask yourself, for a given data retrieval operation, whether the

data in the cache is good enough, or you need absolutely current data from the data source. Then ask

yourself how you want to resolve conflicts between data already cached and duplicate incoming data.

Then consider the process DevForce will use to satisfy the query and make sure it will have the data it

needs to give you a correct result. DevForce gives you the flexibility to set the behavior exactly as need

it.

Making a One-Time Change to the QueryStrategy With Which a Given Query Is Run

You may find yourself with an existing IEntityQuery object that you don’t want to disturb in any way, but

which you would like to run with a different QueryStrategy for a specific, one-time purpose. DevForce

provides an extension method, With(), that permits you to do this.30

When a call to With() is chained to a query, the result may be either a new query or a reference to the

original query. Normally it will be a new query, but if the content of the With() call is such that the

resultant query would be the same as the original one, a reference to the original query is returned

instead of a new query.

If you ever want to be sure that you get a new query, use the Clone() extension method instead of

With(). With() avoids the overhead of a Clone() when a copy is unnecessary.

Code Snippet 42. QueryStrategyWithAndCloning

C#

IEntityQuery<Customer> query00 = _em1.Customers

.Where(c => c.CompanyName.ToLower().StartsWith("a"));

query00.QueryStrategy = QueryStrategy.DataSourceOnly;

// The With() call in the right-hand side of the following statement

// specifies a query that is materially different from query0, in

// that it has a different QueryStrategy associated with it.

// Accordingly, the right-hand side of the statement will return

// a new query:

IEntityQuery<Customer> query01 = query00.With(QueryStrategy.CacheOnly);

// Because the content of the With() call in the right-hand side

// of the following statement doesn't result in a modification

// of query0, the right-hand side will return a reference to

// query0 rather than a new query.

IEntityQuery<Customer> query02 = query00.With(QueryStrategy.DataSourceOnly);

// If you want to be certain you get a new query, use Clone()

// rather than With():

30 Our topic here is QueryStrategy, but in fact some overloads of the With() method also (or alternatively) permit you to make a

one-time change to the EntityManager against which the query will be run.

Page 185: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

185 | P a g e

EntityQuery<Customer> query03 = (EntityQuery<Customer>)query00.Clone();

query03.QueryStrategy = QueryStrategy.DataSourceOnly;

VB

Dim query00 As IEntityQuery(Of Customer) = _em1.Customers.Where(Function(c)

c.CompanyName.ToLower().StartsWith("a"))

query00.QueryStrategy = QueryStrategy.DataSourceOnly

' The With() call in the right-hand side of the following statement

' specifies a query that is materially different from query0, in

' that it has a different QueryStrategy associated with it.

' Accordingly, the right-hand side of the statement will return

' a new query:

Dim query01 As IEntityQuery(Of Customer) = query00.With(QueryStrategy.CacheOnly)

' Because the content of the With() call in the right-hand side

' of the following statement doesn't result in a modification

' of query0, the right-hand side will return a reference to

' query0 rather than a new query.

Dim query02 As IEntityQuery(Of Customer) = query00.With(QueryStrategy.DataSourceOnly)

' If you want to be certain you get a new query, use Clone()

' rather than With():

Dim query03 As EntityQuery(Of Customer) = CType(query00.Clone(), EntityQuery(Of Customer))

query03.QueryStrategy = QueryStrategy.DataSourceOnly

Span Queries A EntityManager query method always returns entities of a single type, the return type identified in the

query object. But what about entities related to the returned entities? When do we get those?

Consider a query for second quarter orders. We display them in a grid with their customer names and

order totals.

The Order entities entered the cache when we processed the query. Not so the Customer and the

OrderDetail entities that we need to calculate the order total. The EntityManager gets these entities

only when we ask for them explicitly. Such delayed fetching we called deferred retrieval.

The grid control binding calls an Order property each time it fills a cell. The “Customer” and “Order

Total” columns are bound to two properties that resolve to two relation queries, one for Customer

entities and one for OrderDetail entities. This means the grid control invokes two relation queries for

each and every row. There are three rows showing in the screen shot so there will be six queries, each

one requiring a round trip to the data source. In other words, filling this grid requires six trips to the data

source.

Page 186: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

186 | P a g e

Now suppose that we had an excellent quarter and placed a thousand orders. The user clicks the

“Customer” column caption, causing the grid to sort by customer. The sort requires examination of

every one of those thousand orders. Most grids will fire every visible property on every examined row.

That could mean two thousand separate trips to the server: one thousand fetches of customers and one

thousand fetches of order details.

The UI will stall for ten uncomfortable seconds and then return to its familiar crisp responsiveness.

Subsequent sorts and scrolling are fast; all of the entities are now in cache so there are no trips to the

data source31.

But those ten seconds felt like an eternity. The problem wasn’t the ten seconds; it’s that they occurred

when the user thought they should not. She expected the search for orders take some time; maybe not

ten seconds but she expected a pause of some length. On the other hand, she expected the sort to

happen immediately. When it didn’t, she thought there was something wrong with the application.

Is the sort delay necessary? Of course not!

The program cannot anticipate needing the related data and so it fetches entities inefficiently. We know

better. When we grab the thousand orders, we can fetch their customers and order details at the same

time. Not every Customer in the data source. Not every OrderDetail entity either. We only need the

customer and order details that are related to those thousand second quarter orders. We should get

them all at once, not piecemeal as we scroll or sort the grid.

Span queries to the rescue. We can add span instructions to our query so that the EntityManager gets

the related entities when it gets the orders. A span query instruction describes a path along the root

entity graph to a particular entity type know as the span target.

Of course, a EntityManager returns references to the root objects when it executes a span query. At the

same time it fetches every span target entity related to any of the returned root entities and puts them

in the cache.

We’ll need two of spans for our example. There is a simple syntax for spanning to the immediate

neighbors of the query’s result entity type:

C#

var query = _em1.Customers

.Include("Orders")

VB

Dim query = _em1.Customers _

.Include("Orders")

31 The volume of data is not the issue. We might think that we‟d improve performance if we used a view that summed the

OrderDetails on the server. We‟d get one value per row instead of having to bring down the details and sum them locally.

When we try this, we observe no improvement whatsoever. The delays were due entirely to the round-tripping, not the data

volume nor the summations.

Page 187: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

187 | P a g e

We can span to entities farther away on the Order business object graph also as we might do if we were

displaying product name in the Order’s OrderDetails grid.

Code Snippet 43. NavigationSynchronousPreload (repeated)

C#

var query = _em1.Customers

.Include("Orders")

.Include("Orders.OrderDetails");

.Include("Orders.OrderDetails.Product");

.Include("Orders.OrderDetails.Product.Supplier");

.Include("Orders.SalesRep")

VB

Dim query = _em1.Customers _

.Include("Orders") _

.Include("Orders.OrderDetails") _

.Include("Orders.OrderDetails.Product") _

.Include("Orders.OrderDetails.Product.Supplier") _

.Include("Orders.SalesRep")

Again, span queries don’t change the list of entities to which references are returned from the query.

The caller still receives the same thousand orders. But before returning the orders, the span query

processing fetches the related entities and merges them into the cache. When the grid cells call upon

Order properties to return customers or calculated order totals, those properties will find the pertinent

entities waiting in cache.

The main order query is a little slower because there are more entities retrieved. The user won’t notice;

she expected the search to take a beat or two. The first sort is instantaneous; she is thrilled.

Performance Details

While spans greatly reduce the number of queries submitted to the database, they do not, of course,

eliminate them altogether. Each span resolves to a separate query and each of these span queries

necessitates a separate trip to the database. Thus, if our we added three spans to an Order query, there

would be four queries (one for the Orders, one for the related type referenced in each of the spans) and

four trips to the database. But these four trips -- as our previous discussion has illustrated – might well

replace thousands of trips required in the absence of spans.

In an n-tier deployment using the Business Object Server (BOS), the picture is even rosier. In that

configuration, the client submits the entire request, including spans, in a single transmission to the BOS.

It is the BOS that makes the four trips to the database. When the BOS has a fast, fat pipe to the

database - as it should – those four trips are very quick indeed. The BOS then combines the results from

its queries against the database into a single package that it ships back to the client. There has been only

one trip across the “slow” connection between client and server!

Note also that the total loads on the EntityServer and database are reduced when each client is making

efficient data requests using spans. Thus, every individual client benefits from the improved efficiency of

the other clients.

Page 188: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

188 | P a g e

Performance matters ... but not all time and effort spent optimizing performance returns equal results.

We strongly advise instrumenting your queries during development and testing to identify performance

hotspots. Then optimize where it really matters.

Cached Entity Lifespan Entities stay in the cache until the application terminates or they are removed. There is no garbage

collection.

We may need to purge the cache of unwanted entities if

we accumulate a large volume of entities during a user session

a session might last a long time – days, weeks, etc.

the cache contents will be saved and later restored from local storage.

The programmer has many “remove” options including the ability to remove a single entity, a list of

entities, entities of a particular type, and all entities with a specified EntityState.

“Removal” and “deletion” are not the same thing. “Remove” means “remove the entity from the cache.”

There are no data source implications. The entity is simply no longer in the cache; it is as if we had never

fetched it.

“Delete” means “schedule the entity for deletion from the data source.” The entity remains hidden in

the cache, waiting for the moment when we send a delete request to the data source. That moment

arrives when we “save” the deleted entity. Once saved (that is, deleted from the data source), the

object is removed from the cache.

New entities are removed from the cache immediately when deleted; they were never in the data

source so there is nothing there to delete, nothing to schedule.

Saving the Cache Locally An EntityManager can save its cache locally. This feature is useful in many scenarios including these

two:

The application must be able to run offline for extended periods. It must be possible to exit the

application and launch it again later while still disconnected.

The developer is worried that the user may accumulate many changes for a long time without saving to

the data source. The application would snapshot the changes periodically in case the application goes

down. But many of the modified business objects won‟t pass data source validity checks or won‟t

satisfy business rules for permanent business objects. They can‟t be saved to the data source.

In the first case the application can’t reach the data source and in the second its access is blocked. The

application needs a local option.

The application can tell the EntityManager to serialize its object cache as an XML stream and save the

stream to a file on the client’s file system. Variations on the theme enable encryption of the stream and

filing to isolated storage or other arbitrary destinations.

Page 189: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

189 | P a g e

On command or when the application is re-launched, the application can locate the file and restore its

contents to the EntityManager’s cache. The developer can choose to completely replace the target

cache or merge the saved cache objects into it; in a merge, objects from the saved cache replace

corresponding objects in the target cache.

The pool of temporary ids maintained by the developer‟s custom implementation of IIdGenerator is also

saved and restored.

The process preserves pending business object changes – additions, modifications, deletes. When the

application next obtains a server connection, it can synchronize local objects with the central data

source. It can refresh local unmodified copies of business objects that have been changed by other

users. It can save local pending changes, relying upon DevForce optimistic concurrency checking to

prevent overwriting other users’ changes.

If the developer expects the application to operate offline, she should prep the cache by retrieving the

business objects the user is likely to need before disconnecting and saving the cache locally. While

disconnected, queries and object navigation can only access objects already in cache.

The TraceViewer: Watch What Data Is Being Loaded, and How

Sometimes you may not be aware of what data is being loaded during particular processes. In this, the

DevForce TraceViewer can be extremely helpful. It monitors all communications with the business

object server, providing a real-time log of same.

There are two different ways to use the Trace Viewer:

Stand-alone, and

Embedded in your application.

To use the Trace Viewer in stand-alone mode, you will typically launch it from the Windows Start Menu

for DevForce:

You can use the Trace Viewer in this mode with no change to your application code, but only if run your

application in n-tier mode, with the Business Object Server running in a separate process from the client

application. You can also use the stand-alone Trace Viewer without running n-tier if you are willing to

add a single line of code to your application.

Page 190: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

190 | P a g e

Embedding the Trace Viewer in your application requires a couple of minor (and isolated) changes to

your application code, but offers greater convenience – you can set it to begin working automatically

whenever you start the app – and it does not require that the Business Object Server be launched in a

separate process. For our own development work, in non-release versions of our applications, we often

use the Trace Viewer this way.

We’ll detail both approaches in the following material.

Using the Trace Viewer Stand-Alone To use the Trace Viewer stand-alone, launch it from the Windows Start Menu entry shown in the screen

shot above. It will display a dialog window like the following:

Once launched, the Trace Viewer makes periodic attempts to connect with a TracePublisher. It will find a

Business Object Server instance once one is running.

Using the Stand-Alone Trace Viewer

While Running N-Tier

To see the server activity instigated by your

application without making any code changes, you‟ll

need to launch the app in n-tier mode. You can do that

easily, on a single development machine, using the N-

Tier Configuration Starter utility which you will also

find on the IdeaBlade DevForce / Tools menu. You

can find step-by-step instructions for working with the

N-Tier Configuration Starter in the Deployment topic

document, in the section “N-Tier Configuration

Starter”.

Once your application in running n-tier, you’ll see communications with the BOS logged as follows:

Page 191: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

191 | P a g e

The activity logged just above resulted from execution of the following method in an app:

C#

private void DoIt() {

_mgr.Customers.ToList();

_mgr.Orders.ToList();

_mgr.Products.ToList();

_mgr.Suppliers.ToList();

_mgr.Employees.ToList();

}

VB

Private Sub DoIt()

_mgr.Customers.ToList()

_mgr.Orders.ToList()

_mgr.Products.ToList()

_mgr.Suppliers.ToList()

_mgr.Employees.ToList()

End Sub

The method simply fires off five queries that must hit the server to get their data.

Using the Stand-Alone Trace Viewer While Running “Single -Tier”

(Client and Server in the Same Process)

To see activity in the stand-alone Trace Viewer when running in single-tier, development mode, you

must add one line of code to your application:

C#

TracePublisher.LocalInstance.MakeRemotable();

Page 192: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

192 | P a g e

VB

TracePublisher.LocalInstance.MakeRemotable()

Once your app has made the above call to MakeRemotable(), it begins functioning as a TracePublisher,

doing so on a default port and default service name that matches the defaults on the Trace Viewer. You

can also make it a publisher on a different port and with a different service name, but then you will need

to change the settings on the Trace Viewer to listen on the specified channel.

C#

TracePublisher.LocalInstance.MakeRemotable(9010, "MyClientService");

VB

TracePublisher.LocalInstance.MakeRemotable(9010, " MyClientService")

For most uses, you probably won’t find it necessary to change the port or service name.

Note that when a DevForce app is deployed n-tier, separate sets of messages are published server-side

and client-side. (These messages end up in the server- and client-side debug logs, as well as in any Trace

Viewers that are listening for them.) When running single-tier, messages written by the (logically server-

side) EntityService (which in single-tier mode runs inside the same process as the client application) will

be published along with messages from the logical client-side. You’ll see everything. Here are the

messages captured by the stand-alone Trace Viewer after adding the MakeRemotable() call and running

single-tier:

Note that including the call to MakeRemotable() and running the stand-alone Trace Viewer is the only

way to use the Trace Viewer with a (single-tier) console app. The options for embedding the

TraceViewer (described below) require WPF or WinForm applications.

Page 193: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

193 | P a g e

Embedding the Trace Viewer in Your Application For convenience during development, you may prefer to embed the Trace Viewer in your application.

This requires adding a reference to the TraceViewer executable and adding a line or two of code.

There are actually two different implementations of the TraceViewer within DevForce: one for WinForm

apps and one for WPF apps. The names of their executables are as shown below:

Target Application Type Executable

WinForm App WinTraceViewer.exe

WPF App WPFTraceViewer.exe

To use either TraceViewer in your app, you must first set a reference (in your app’s UI project) to the

executable file where it lives. Both versions of the TraceViewer are deployed to the DevForce installation

folder, usually C:\Program Files\IdeaBlade DevForce.

Embedding the WPFTraceViewer in Your WPF App To add the reference to the WPF TraceViewer, for example, right-click the references node in your

desired UI project, and select Add Reference. On the Add Reference dialog, select the Browse tab, then

browse to the file and click OK:

Here’s some code for the startup window of a simple WPF app. The code launches the WPF TraceViewer

during initialization, and includes a button click handler that launches a query for some data:

Page 194: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

194 | P a g e

C#

namespace Wpf01 {

/// <summary>

/// Interaction logic for Window1.xaml

/// </summary>

public partial class Window1 : Window {

public Window1() {

InitializeComponent();

SetUpTraceViewer();

}

private void SetUpTraceViewer() {

WPFTraceViewer tv = new WPFTraceViewer();

tv.Show();

}

private void _loadDataButton_Click(object sender, RoutedEventArgs e) {

List<Customer> customers = _em1.Customers.Where(c => c.Country == "Brazil").ToList();

_outputTextBlock.Text += String.Format("Customer retrieved: {0}\n", customers.Count);

}

#region Private Fields

DomainModelEntityManager _em1 = new DomainModelEntityManager();

#endregion Private Fields

}

}

VB

Public Class Program

Public Shared Sub main()

#If DEBUG Then

Dim tv As New IdeaBlade.DevTools.TraceViewer.TraceViewerForm()

tv.Show()

#end If

Application.Run(MainForm)

End Sub

End Class

Here is the display that results:

Page 195: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

195 | P a g e

The TraceViewer logs all operations against the Entity Server, so you can use it to see exactly what data

loading operations result from actions performed in the user interface. In this app, additional clicks of

the <Load Data> button result in no further activity against the Entity Server, since the desired

Customers, once retrieved into the cache, can be accessed there thenceforward.

Embedding the WinTraceViewer in Your WinForms App To add the reference to the WinTraceViewer, right-click the references node in your desired UI project,

and select Add Reference. On the Add Reference dialog, select the Browse tab, then browse to the file

and click OK:

Page 196: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

196 | P a g e

Here’s some code for the startup program of a simple WinForm app that launches the WinForms

TraceViewer during initialization:

Page 197: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

197 | P a g e

C#

static class Program {

[STAThread]

static void Main() {

Application.EnableVisualStyles();

Application.SetCompatibleTextRenderingDefault(false);

IdeaBlade.DevTools.TraceViewer.TraceViewerForm tv =

new IdeaBlade.DevTools.TraceViewer.TraceViewerForm();

tv.Show(); Application.Run(new _customerForm());

}

}

VB

The main method, after launching the TraceViewer, launches _customerForm as the startup form:

Page 198: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

198 | P a g e

The handler for the button’s click event launches a query for some Customers:

C#

private void _loadDataButton_Click(object sender, EventArgs e) {

_customers.ReplaceRange(_em1.Customers

.Where(c => c.Country == "Brazil"));

}

VB

When you click the button, you see activity logged in the TraceViewer:

The TraceViewer logs all operations against the Entity Server, so you can use it to see exactly what data

loading operations result from actions performed in the user interface. In this app, additional clicks of

the <Load Data> button result in no further activity against the Entity Server, since the desired

Customers, once retrieved into the cache, can be accessed there thenceforward.

Page 199: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

199 | P a g e

Getting Generated SQL to Display in the TraceViewer By default, both TraceViewers (WPF and WinForms) show queries in a LINQ-like representation:

The above, for example, is an unrestricted query for entities of type Employee.

You can, however, elect to see the SQL generated server-side by the Entity Framework. To do that, you

must change the logTraceString setting in the applicable app.config file32 to true. Note that

logTraceString is an attribute of a particular edmKey (which represents a single data source).

This results in a display like the following:

The TraceViewer can be invaluable in troubleshooting performance problems. These are often caused by

inefficient data retrieval (such as loading a data grid where each rows triggers several trips to the server

to pick up related objects that were not pre-loaded).

32 In a development app with all parts running on a single machine, choose the App.Config file in the AppHelper project.

Page 200: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

200 | P a g e

Using the Trace Viewer With a Silverlight App For Silverlight applications, the EntityService automatically runs in a separate process from the client, so

a stand-alone TraceViewer will automatically pick up the server messages associated with your app

(assuming you haven’t changed the default service name and port).

If you wish to see client-side Trace messages, you will need to embed a UserControl into your Silverlight

front end. Here is the XAML for the control...

XAML

<UserControl x:Class="DevForceSilverlightApp.TraceWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:data="clr-

namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

Width="Auto" Height="Auto">

<Grid x:Name="LayoutRoot" Margin="20,20,20,20" >

<data:DataGrid

x:Name="_dataGrid"

HorizontalAlignment="Left"

VerticalAlignment="Top"

AutoGenerateColumns="True"

MinWidth="250"

MinHeight="100"

Background="#FFB5BAB5"

Margin="0,0,20,0"

IsReadOnly="True"

/>

</Grid>

</UserControl>

...and here is the code behind:

C#

using System;

using System.Collections.ObjectModel;

using System.Windows.Controls;

using IdeaBlade.Core;

namespace DevForceSilverlightApp {

/// <summary>

/// Sample trace subscriber. You can drop the TraceViewer UserControl onto a page

/// to display tracing information from the Silverlight application in a grid.

/// </summary>

/// <remarks>

/// To use the TraceSubscriber: 1) listen for its Publish event, and 2) call

StartSubscription()

/// to have tracing messages sent to you. You can also call StopSubscription()

/// to temporarily or permanently stop receiving messages.

/// </remarks>

public partial class TraceWindow : UserControl {

Page 201: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

201 | P a g e

public TraceWindow() {

InitializeComponent();

_messages = new ObservableCollection<TraceMessage>();

_subscriber = new TraceSubscriber();

_subscriber.Publish += new EventHandler<PublishEventArgs>(_subscriber_Publish);

_subscriber.StartSubscription();

_dataGrid.ItemsSource = _messages;

}

private void _subscriber_Publish(object sender, PublishEventArgs e) {

_messages.Add(e.TraceMessage);

if (_dataGrid.Columns.Count > 0) {

_dataGrid.ScrollIntoView(e.TraceMessage, _dataGrid.Columns[0]);

}

}

TraceSubscriber _subscriber;

ObservableCollection<TraceMessage> _messages;

}

}

VB

Be sure to change the namespace in both the XAML and the code to match your app!

In the following, we have embedded the above TraceWindow UserControl in another UserControl:

XAML

<UserControl x:Class="DevForceSilverlightApp.ConsoleUserControl"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:local="clr-namespace:DevForceSilverlightApp"

>

<Grid x:Name="LayoutRoot" Background="White">

[... snip]

<ScrollViewer

x:Name="_traceWindowScrollViewer" Grid.Row="1" Margin="0,0,20,0">

<local:TraceWindow />

</ScrollViewer>

[... snip]

</Grid>

</UserControl>

That’s enough to get it to display client-side trace messages written by DevForce. We can add our own

trace messages as follows...

Page 202: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

202 | P a g e

C#

IdeaBlade.Core.TraceFns.WriteLine("Hello world!");

VB

IdeaBlade.Core.TraceFns.WriteLine("Hello world!")

...resulting in the following output (the TraceWindow control contains the DataGrid):

Creating Business Objects

In this short chapter we discuss business object creation in a bit more detail. We’ll explain why and

when the developer must write her own creation method and what minimal steps are essential to its

implementation.

We delve into the special challenge of creating unique business object identifiers and how DevForce

supports this process.

We mention also two other custom class methods, CompareTo() and ToString(). We may want to add

them while writing the creation method.

When Not to Create A business object class needs a create method only if the application can add new business objects of its

type. This is not so in a surprising number of cases. For example, we don’t add states to the USA very

Page 203: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

203 | P a g e

often. Our application may want access to these states as business objects but it is unlikely to need to

add new ones (or change existing states).

The Business Object Create Method Most applications will add new instances to many of its business object classes. The developer must

write a Create method for each of these classes and call it whenever she wants a new object.

For technical reasons, we must acquire new instances via a class method rather than by means of a

constructor. The expression emp = new Employee(…) is always invalid; instead it must look something

like emp = Employee.Create(…).

Most Create method implementations return a single business object after following these four steps:

1. Ask the EntityManager for a prototype of the new business object

2. Give the prototype a unique identity

3. Fill in some of its initial values (optional)

4. Add the completed prototype to the EntityManager„s cache

Why can’t DevForce take care of this for us? Because steps 2 and 3 require application-specific know-

how that DevForce can neither discover nor supply.

Step #2 concerns the identity of the object. DevForce requires that every business object have a unique

identity. Identity is captured in the object’s primary key which is composed of one or more identifiers.

There is no way for DevForce to know how identifiers are determined. While it can discover that a

particular database table’s key is a single integer field, this fact is insufficient to generate an identifier.

The integer could come from anywhere.

Step #3 concerns the validity of the object. It is generally a good idea to maintain an object in a valid

state. This isn’t always possible but it is a useful goal and the Create method is a place to start. Of course

DevForce is ignorant of application business rules so if there is to be any object initialization it is up to

the developer to code it here.

Generating unique identifiers Unless the primary key is an Identity column, DevForce doesn’t know how to generate an object’s

primary key identifier(s) so it cannot deliver new business objects on its own. That is why the

EntityManager provides a prototype in Step #1 that is not yet in the cache.

Once the developer sets the primary key’s identifier(s) in the prototype, the prototype may be added to

the EntityManager’s cache and become a business object accessible to the application. It may still be

invalid from a business perspective but it is programmatically acceptable to DevForce.

A business object’s key must be unique not only within the context of the current user session but

across the application domain. We have to make sure the key we assign to a new employee object

cannot also be assigned to a different employee object by someone else.

Page 204: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

204 | P a g e

GUIDs

GUIDs (globally unique identifiers) make great identifiers (aka “ids”) because they are easy to mint, are

nearly certain to be unique, and can be generated locally, independent of any external resource. If we

are in complete control of the database schema design, GUIDs are the way to go.

The MS SQL uniqueidentifier data type is the database analog for a GUID.

When we need a new GUID, we ask .NET to compute one for us, assign it to the prototype’s identifier

data member, and move on to the next step in object creation.

GUIDs have two disadvantages:

GUID values are long and obscure. Users find them difficult to type correctly and difficult to remember.

At 16 bytes, the GUID is large compared to other data types such as 4-byte integers. Database indexes built

using GUID keys may be relatively slower than indexes using an integer key.

In our experience, striving for meaningful identifiers leads to disappointment and failure; we strongly

council against using identifiers with semantic content. If you disagree, you may regard these additional

GUID properties as disadvantageous:

GUID values are random and cannot accept any patterns that may make them more meaningful to users.

There is no way to determine the sequence in which GUID values are generated. They are not suited for

applications that depend on incrementing key values.

If GUIDs work for you, you may skip the next section on custom id generation.

Unfortunately, few of us have this option. We are usually given a database that we cannot change.

We’re not allowed to replace all table ids and all foreign key columns with 16 byte integer GUIDs. We

have to conform to the existing key schemes which impose both the identifier data types and the

manner of their generation.

Store-Generated Ids

Store-generated Ids are those Id fields marked as store-generated in the EDMX. These Ids will typically

be generated as SQL Server identity columns or Oracle sequences. DevForce detects these fields and

generates temporary ids automatically. DevForce also automatically performs fixup on these temporary

Ids after a save has occurred. When using store-generated Ids you do not need to implement a custom

IIdGenerator of your own, or otherwise worry about setting these Ids in your code.

Custom id generation

Custom id generation almost always requires access to some external resource, some application-

specific logic for deriving new ids, and additional logic to increment the resource.

Suppose our application uses integer keys for all of its tables. The database has a special “NextId” table

that holds the next integer id. To get a new id, a server-side process could quickly lock that table, grab

the id, update the table to hold a new next id, and free the table.

Page 205: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

205 | P a g e

This is just one among thousands of ways applications generate ids. The commonality is the external

resource, the functional equivalent of the NextId table, without which we could not be sure of

generating identifiers that are unique within the application domain.

The developer must write the code that reads the resource, calculates ids, and updates the resource.

If only it were this simple. Remember that we are describing a smart client application in which new

object creation begins on the client machine. The client machine could be disconnected and thus unable

to reach a NextId table or some other external source of permanent ids.

We still want to be able to create new objects while disconnected. We know that we will have to

connect to that external resource to get permanent ids and store the new objects in the database. In the

interim, we must finesse the situation and use locally generated, temporary ids until we can reconnect

and replace them with permanent ids.

For example, since our permanent ids are always positive integers, we could use negative integers for

temporary ids, acquiring them by decrementing a client-side counter.

We assign temporary ids (however generated) to the new objects and to the foreign keys of the objects

that reference them. At some point when we’re sure we’re connected, we run around to all the

locations with temporary ids and replace them with permanent ids.

Id Fix-up

Just before we save objects back to the database is a good time to attempt this fix-up because (a) we

must be connected to save and (b) we must fix all locally modified objects before saving any of them in

case one such object has a reference to a temporary id.

The DevForce id generation facility can help. In essence:

The developer writes an id generation class that conforms to the DevForce IIdGenerator interface. This

class will handle id generation for every class of business object in the data source.

The developer implements the prescribed methods in the id generation class that provide temporary and

permanent ids.

Back in the business object creation method, the developer invokes the

EntityManager.GenerateId(…) method which assigns a temporary id to the new object prototype. A

typical call looks like: pm.GenerateId(protoEmp, Employee.IdEntityColumn).

The EntityManager attempts a save

The DevForce framework tells the developer‟s id generation class to give it the map of temporary ids to

permanent ids.

The framework runs around the cache, replacing temporary ids with permanent ids.

Foreign Key Fix-Up

The framework replaces temporary ids in entity properties that are connected to the generated id

column by a relation. The generated Id column in this example is the Employee.IdEntityColumn.

Page 206: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

206 | P a g e

Suppose there is a relation defined in the model between Order.SalesRepId and EmployeeId, but that

no relation is defined between Customer.SalesRepId and EmployeeId. This is a critical omission, as you

will see.

We create a new employee, myEmployee. The Id generator gives him a temporary id value of –1. During

the application session myEmployee is assigned to myOrder:

myOrder.SalesRep = myEmployee

But no relation was defined between Employee and Customer, so there is no Customer.SalesRep

property33 to which myEmployee can be assigned directly. Nevertheless, the determined developer

stuffs the EmployeeId value directly into the Customer.SalesRepId property.

myCustomer.SalesRepId = myEmployee.Id

This is a bad practice and should be avoided. The absence of the myCustomer.SalesRep property

should have been a warning that a critical relation was missing. See what happens:

The user saves and the fix-up begins.

The value of myEmployee.Id is updated to its permanent value, 301.

myOrder.SalesRepId is fixed up to 301 (since there is a relation back to Employee.Id) .

myCustomer.SalesRepId stays stuck with id = –1. (There was no relation from

myCustomer.SalesRepId back to Employee.Id so the PM didn‟t know to replace the SalesRepId.)

Not good!

In most cases the end result of all this would be an errant foreign key value persisted to the data source.

If, however, the data source did have the necessary foreign key constraint (but the related relation had

been deleted from the model), the result of attempting to persist the errant foreign key value would be

a foreign key constraint exception. That might appear to reflect a PersistenceOrder problem (e.g., saving

a child before saving its new parent) when in fact it is not.

Important: Map all of the relations.

Sample Id Generator

DevForce ships with source for example id generator classes that you can either use directly or adapt for

your application.

It‟s now easy to see why we prefer GUIDs. We can use .NET‟s free GUID generator while disconnected

because it works locally without resort to an external resource. GUIDs are globally unique so the ids we

create are fine as permanent ids. All of the complexity disappears. The 16-byte cost of GUIDs is usually

worth it. Use GUIDs if you can.

Ids in mapping objects

33 At least, there would be no such property generated by the DevForce Object Mapper

Page 207: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

207 | P a g e

We cannot leave this subject without observing that some business objects do not use generated ids.

Mapping objects relate one kind of business object to another in a many-to-many relationship.

OrderDetails in the NorthwindIB database is one such business object. In addition to carrying

information about a particular purchase item such as quantity and price, it relates Orders to Products in

a many-to-many relationship. Orders have many items each associated with a particular product being

purchased. A given product will appear as a purchased item on many different orders.

A mapping object’s primary key is typically a combination of the ids from the two objects it relates. The

key of an OrderDetail object is comprised of its parent order’s id and the id of the product being sold. It

is an {OrderId, ProductId} tuple.

In such cases, the ids that form the primary key are not generated within the create method but rather

passed into the method in its parameter list. The create method for OrderDetail would include an order

object and a product object among its parameters. Inside the Create() method, we would extract their

ids and set the OrderDetail prototype’s primary key accordingly.

OrderDetail happens also to be the detail object in a master/detail relationship. The id of the master

object is often one of the identifiers in the detail object’s key and it will usually be passed into the

method in one of its parameters.

Creating a valid business object EntityManager delivers a prototype in step #1 of the create method. DevForce ensures that the

prototype has a non-null value for every object member that is mapped to a non-nullable field in the

database. This “assistance” is often helpful but it may be wrong.

Suppose business rules demand that every employee have a hire date and that hire dates must be later

than the company’s incorporation. The HireDate field in the database is mandatory so the prototype

carries a default value for the corresponding object data member. The developer should make no

assumption about this value other than that it is a valid date from the perspective of the database. It

could be anything and might well be a date prior to the founding of the company.

The hire date is probably unknown when the object is created. The developer may choose to wait until it

becomes known in which case the prototype default value will suffice for awhile. The object can’t be

saved but we will have time to get a valid hire date from the user before we save it.

Alternatively, the developer may decide that a particular date, such as today’s date, makes a good initial

hire date. She will initialize the prototype’s hire date accordingly, here in the Create method.

The lesson: strive to make the new object as valid as possible by setting appropriate initial values in this

step of the creation method. It is often helpful to add parameters to the Create method so the caller can

pass in appropriate initial values. None of this is required but it is good practice.

Page 208: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

208 | P a g e

Auxiliary Business Object Class Methods While we’re adding a new object creation method inside the business object class, it’s a good time to

mention two other useful methods: CompareTo() and ToString().

CompareTo()

When DevForce sorts a collection of business objects it often looks to the class CompareTo() method to

determine which of two objects sorts before the other.

Business objects inherit a CompareTo() method from the root class of all business objects, Entity. It’s

rarely what we want; the results are arbitrary and unpredictable.

We should override it with a comparison that is useful. A CompareTo()for the Employee class might

compare employee first and last names.

ToString()

It is common for both DevForce and .NET to invoke an object’s ToString() method. An object’s default

ToString() returns the object’s class name. This is rarely useful. For example, anEmployee.ToString()

might return “Tutorial.Entities.Employee”. We should override the Employee ToString() method so

that it returns something useful like “Nancy Davolio”.

Many classes, not just business object classes, should have their own ToString() methods.

Adding and Removing Related Objects using Add() and Remove() Navigation properties that return a collection (e.g., anEmployee.Orders) have Add() and Remove()

methods.

Add()

The Add() method takes a parameter of the type contained by the collection (e.g., an Order).

Code Snippet 44. AddUsingAdd()

C#

Order anOrder = new Order();

anOrder.OrderDate = DateTime.Today;

anOrder.FreightCost = Convert.ToDecimal(999.99);

anEmployee.Orders.Add(anOrder);

Dim anOrder As New Order()

anOrder.OrderDate = Date.Today

anOrder.FreightCost = Convert.ToDecimal(999.99)

anEmployee.Orders.Add(anOrder)

Page 209: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

209 | P a g e

Invoking Add() adds the supplied item to the collection. If the relation between the parent and child

types is 1-to-many and the supplied item is currently associated with a different parent, then Add()

simultaneously removes it from the corresponding collection of the other parent.34

Note that, in the above snippet, we did not need to set the SalesRep property of the new Order to the

Employee whom we wanted to become its parent:

C#

// anOrder.SalesRep = anEmployee; //don't need this; Add() will handle it

VB

' anOrder.SalesRep = anEmployee '' don't need this; Add() will handle it

Invocation of the Add() method on anEmployee.Orders produced the equivalent result.

Remove ()

Remove() also takes a parameter of the type contained by the collection. It dissociates the indicated

instance from the collection’s parent35.

C#

anEmployee.Orders.Remove(anOrder);

VB

anEmployee.Orders.Remove(anOrder)

Note that while Remove unassigns the Order from the target Employee, removing it from the collection

returned by the navigation property, it does not remove it from the cache or mark it for deletion. If you

want the Order removed from the cache or deleted from the back-end datastore, you must order those

actions separately by calling the Order’s EntityAspect.Remove() or EntityAspect.Delete()

methods, as appropriate.

Add() and Remove () on Many-to-Many Navigation Properties

You can also use Add() and Remove () on many-to-many navigation collections generated by the Entity

Data Model. You get these in your Entity Data Model when two entities are linked by a many-to-many

linking table that has “no payload”; that is, no columns other than the two foreign keys (which also form

34 The equivalent result on table rows in a relational database is that the child entity’s foreign key value

is changed.

35 Speaking again of the equivalent result on table rows in a relational database, the child entity’s foreign

key value is set to null.

Page 210: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

210 | P a g e

a composite primary key).36 An example would be an Employee linked to a Territory by means of an

EmployeeTerritory table whose composite primary key consists of the two foreign keys EmployeeId and

TerritoryId, and which has no other columns.

When you have such an association, invoking Add() on the many-to-many navigation property creates

(in the EntityManager cache) the necessary linking object in the EntitySet for the linking objects37.

Remove() marks as deleted the linking object that formerly connected the two entities in the many-to-

many relationship. Both changes – the insertion of a new linking object or the deletion of an existing

one – are propagated to the back-end data store upon the execution of SaveChanges() on the governing

EntityManager.

Adding and Removing Items in Custom-Coded Many-to-Many Navigation Properties

You can (and probably will) also have in your model many-to-many associations involving linking entities

that do have payload. (For example, in the NorthwindIB database, Order links Employees (who act as

sales reps) to Customers in a many-to-many relationship.) For these cases, you should add and remove

elements to the m-to-m collection (e.g., anEmployee.Customers) by inserting or deleting instances of

the linking entity. Since that linking entity is probably significant in its own right (again consider an

Order), it likely has properties that need their values set at creation time in any case.

For example, the following code will have the indirect effect of adding a new Customer to the Customers

collection of anEmployee, but only if the Order being added is for a Customer with which anEmployee is

not already linked through some other Order. Otherwise, aCustomer is already in anEmployee’s

Customers collection.

C#

// May add a Customer to anEmployee‟s Customers collection

anOrder = Order.Create(_entityManager, aCustomer, anOrderDate);

anEmployee.Orders.Add(anOrder);

VB

' May add a Customer to anEmployee‟s Customers collection

anOrder = Order.Create(_entityManager, aCustomer, anOrderDate)

anEmployee.Orders.Add(anOrder)

Similarly, the following code will have the indirect effect of removing aCustomer from the Customers

collection of anEmployee, but only if anEmployee has no other Orders for aCustomer. If she does, then

aCustomer will remain in her Customers collection.

36 See the appendix “Many-to-Many Associations in the Entity Framework” in the Object Mapping chapter for more information.

37 Note that those objects are not exposed in the conceptual model, and are never manipulated directly by you.

Page 211: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

211 | P a g e

C#

// May remove a Customer from anEmployee‟s Customers collection

anOrder.EntityAspect.Delete();

VB

' May remove a Customer from anEmployee‟s Customers collection

anOrder.EntityAspect.Delete()

Business Object Creation Review Developers will write a creation method for each business object class that can add new objects. That

method will return a new business object after it

gets a prototype from the EntityManager

assigns an id to the prototype

(optionally) sets certain prototype values to satisfy minimum standards of validity

adds the prototype to the EntityManager

If we have to generate custom ids for our business objects, we probably will write and register an id

generation class that conforms to the DevForce IIdGenerator interface.

Saving Business Objects

Add, change, and delete operations only affect entities in a EntityManager cache. They are not written

to the data source nor are they visible to other application users until the application tells the

EntityManager to save them. Alternatively, the application can undo the changes rather than save

them.

If the application decides to save, it issues one of the overloads of EntityManager.SaveChanges() that

can save an individual business object, an arbitrary list of objects, or all entities with pending changes.

Saves are always transactional in DevForce.

If concurrency checking is enabled, DevForce will confirm that entities being saved have not been

modified or deleted by another process since they were last retrieved.

This chapter elaborates on each of these points.

EntityState of an Object Unmodified entities are never saved. Attempts to save them are ignored.

The application can determine if a particular object is new, modified, marked for deletion, or unmodified

by examining its EntityState property which returns one of the corresponding EntityRowState

enumerations.

Page 212: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

212 | P a g e

The application can also query the cache for all entities that are in one particular EntityState or

specific combination of EntityStates and submit them together for save.

Undo Modified business objects don’t have to be saved. The application can undo changes made to a single

object or a list of objects in the cache.

This is a single level undo. Undoing a pre-existing object, whether changed or marked for deletion,

restores it to its state when last retrieved from the data source38; its EntityState becomes

“unmodified.” Undoing a newly created object deletes it immediately and removes it from the cache.

There is no undo of an undo.

Validation The wise developer will validate business objects before saving them.

Many developers perform validity checks in the presentation layer. Some checks in the UI make sense

especially when they provide crisp and immediate user feedback.

But good design keeps most validation logic out of the presentation layer and delegates it to the

business object. Here are four good reasons:

As the application evolves there are likely to be multiple screens – even multiple UIs – updating the

same business object. There is high risk that they will perform validation differently and omit essential

checks if each handles its own validation.

The object may be changed by a batch program or by a web service. We need to perform the same

validations in these modes as we do in a graphical interface.

Cross-field and cross-record checks in the UI can create deadlocks and recursion problems. It‟s easier to

apply rules such as “the birth date comes before the hire date” and “orders weighing more than 100

pounds must be shipped by ground” after the user presses a button rather than try to enforce them while

the user is typing.

It‟s easier to break up or combine forms in an interface if you don‟t also have to juggle the validation

code to match.

DevForce offers extensive facilities for defining and executing validation logic. See the chapter,

“Validation Through Verification”.

Temporary Id Fix-up Initiation of any save operation causes the EntityManager to attempt to replace temporary ids with

permanent ids. Subsequent success, failure, or cancellation is immaterial. The act of saving launches the

fix-up process. The fix-up process was covered above, in the section “Id Fix-up”. Be sure you understand

the fix-up process as detailed in that section.

38 Technically, undoing a modified entity sets the “current” version of the entity to its “original” version. Entity versions are

covered in “Advanced Business Object Concepts”.

Page 213: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

213 | P a g e

Life Cycle Events Creation, retrieval, modification, removal, deletion, and save are key moments in a cached entity’s life

cycle. DevForce raises events on these occasions. The developer can subscribe and react accordingly.

Client-Side Life Cycle Events Client-side life cycle events on the EntityManager include Fetching, Fetch, Saving, and Saved. These are

summarized in Table 6:

Table 6. EntityManager Life-Cycle Events

Event Typical Uses of the Corresponding Event Handler

Fetching Modify the query being submitted, or refuse the request for data.

Queried Modify the objects that were returned by the query

Saving Modify the object submitted for saving, or refuse the request to

perform inserts and/or updates.

Saved Modify the saved object (which might be different from the object

submitted for saving by virtue of triggers that were fired on the back

end to modify the latter after it was saved).

The EntityManager raises a Fetching event shortly after the application initiates a data retrieval

operation. It raises a Fetched event if any entities are retrieved successfully. We can add our own event

handlers to these events.

The Fetching event provides the handler with a copy of the query object that the caller proposes to

submit. The event handler can scrutinize the query object, modifying it or rejecting the query entirely if

security or other considerations make that the appropriate response.

The EntityManager raises the Queried event if any entity is retrieved. The handler receives a list of the

entities that were retrieved.

The EntityManager raises a Saving event shortly after the application initiates a save. It raises a Saved

event if any entities are saved successfully. We can add our own event handlers to these events.

The Saving event provides the handler with a list of entities that the caller proposes to save. It will

calculate that list if the method parameters do not prescribe the list39. The event handler can scrutinize

the list, invoke validation methods on selected entities, clean up others (e.g., clear meaningless error

conditions), add additional entities to the list, and even exclude entities from the list. Lastly, it can cancel

the save.

The EntityManager raises the saved event if any entity is saved. The handler receives a list of the

entities that were saved successfully.

In transactional saves, either every entity in the save list is saved or none of them are. In DevForce,

saves are always transactional, even across disparate back-end data sources.

39 SaveChanges() with no arguments, for example, is a blanket request to save every changed entity in cache.

Page 214: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

214 | P a g e

Server-Side Life Cycle Events

Server-side life cycle events on the EntityServer are handled by two “interceptors” – the

EntityServerQueryInterceptor and the EntityServerSaveInterceptor. These are summarized in

Table 7.

Table 7. PersistenceServer Life-Cycle Events

Event Typical Uses of the Corresponding Event Handler

AuthorizeQuery Determine if the user is authorized to make the request.

FilterQuery Modify the query as needed.

ExecuteQuery Can intercept immediately before and after the query is executed.

Always call the base method to allow DevForce to execute the query.

AuthorizeQueryResults Allows for authorization of types in the result set.

AuthorizeSave Determine if the user is authorized to perform the save.

ValidateSave Perform server-side validation of the data to be saved.

ExecuteSave Can intercept immediately before and after the save request is

executed. Always call the base method to allow DevForce to

perform the save.

These events provide the developer with the opportunity to do perform server-side, before-the-fact and

after-the-fact operations on both queries and saves. The EntityManager, which resides client side,

provides corresponding client side events: Fetching, Fetched, Saving, and Saved. The developer thus

has complete flexibility to perform centralized processing on data retrievals and updates, client-side or

server-side, as her use case dictates.

Implementing Server-Side Life-Cycle Event Handlers

Unlike the client-side life-cycle events, the server-side

“events” are handled by sub-typing the DevForce base

classes, EntityServerQueryInterceptor and

EntityServerSaveInterceptor, as needed. These classes

reside in the IdeaBlade.EntityModel.Server assembly

(which must, of course, be referenced by the project that

contains the life-cycle handlers).

Once you have provided an implementation of the desired class, make sure that the assembly

containing the implementation is deployed to the server.

Here’s a post-build event that ensures that the Server assembly will be deployed to the executables

folder in a single-machine development environment:

Page 215: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

215 | P a g e

Saves and Transaction Management EntityManager save methods can save a single business object, a list of objects, all objects in a

particular modified state (e.g., “new”), or all entities with pending changes.

Recall that modified objects include additions, updates, and deletes. Deleted records are actually marked

for delete and must be “saved” to be deleted from the data source.

EntityManager saves are transactional by default. When the developer saves more than one entity at a

time, DevForce processes them together as a single unit of work. Either every save succeeds, or they are

all rolled back.

Behind the scenes, DevForce causes the necessary INSERT, UPDATE, and DELETE statements to be

wrapped within “Begin Transaction” and “Commit Transaction” or “Rollback Transaction” statements. If

all succeed the transaction is committed. If any fail, the data source is restored to its pre-transaction

condition40.

The application relies upon the data source manager to provide two key benefits throughout the

transaction:

40 We cover save failures in topic coming up soon.

Page 216: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

216 | P a g e

Consistency - simultaneous queries and change requests cannot collide with each other, and users must

never see or operate on data that is in mid-change. In a multi-user environment the data source

manager must prevent simultaneous queries and data modification requests from interfering with each

other. This is important because if the data being processed by a query can be changed by another

user's update, the results of the query may be ambiguous.

Recovery - in case of system failure, data source recovery is complete and automatic.

SQL defines different degrees of consistency enforcement called “isolation levels”. Each database vendor

has a different default isolation level and a proprietary syntax to change it. The developer is responsible for

setting the database isolation level and all other global database behavior options. Such settings may be

made in the database itself or with proprietary information embedded in the connection string. Consult the

database vendor‟s documentation.

Distributed Transactions

DevForce can provide transactional integrity when saving entities to two or more data sources. These

data sources can be of different types from different vendors. Their data source managers must support

the X/Open XA specification for Distributed Transactions41.

The developer instructs DevForce to use the .NET Enterprise Services (AKA, COM+) Distributed

Transaction Coordinator (DTC) to handle transaction management.

DTC performs a two phase commit. In the first “prepare” phase all parties to the transaction signal their

readiness to commit their parts of the transaction. In so agreeing they must guarantee that they can

complete their tasks even after a crash.

If any participant does not agree, all parties roll back the transactions they have underway.

If all participants agree, the transaction moves into the second, “commit” phase in which the parties

actually commit their changes.

If the transaction is successful, the entities are re-queried.

Re-query After Save DevForce immediately re-queries the entity after inserting or updating it successfully. Re-query is

essential because the insert or update may provoke a data source trigger that modifies the data source

object. We often use a trigger to update an optimistic concurrency column. A database-maintained

timestamp is another common example. In such cases, the row in the database is no longer exactly the

same as the row we wrote.

The EntityServer must update the entity and then send it back to the client’s EntityManager. The

revised entity re-enters the cache, replacing its original; its EntityState becomes Unchanged.

41 At this writing, databases are the only DevForce supported data sources that support the X/Open XA protocol.

Page 217: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

217 | P a g e

When Save Fails The EntityManager.SaveChanges() method overrides all return a SaveResult object.

SaveResult.Ok returns “true” if the save was entirely successful. If the save was cancelled in a

ServerSaving handler, SaveResult.WasCancelled will return “true” and SaveResult.Ok will return

“false”. If the save failed for any reason, the PM throws an EntityManagerSaveException.

This is the default behavior. You can change that behavior – indeed, SaveChanges() used to behave

differently in versions prior to 3.1.3 – but we recommend that you stay with this default.

It follows that you prepare your code to catch and analyze an exception. You will find the information

you need in the exception, including the SaveResult object that SaveChanges() would have returned.

Always handle SaveChanges exceptions.

Do wrap every call to EntityManager.SaveChanges() in your own custom Save method.

Do wrap every SaveChanges in a Try/Catch and analyze the exception when thrown.

Here’s a code fragment showing a Save method that matches our recommendation:

Code Snippet 45. WhenSaveFails()

C#

internal void SaveAll() {

try {

using ( new WaitCursor(Page.ParentForm) ) {

MainEm.Manager.SaveChanges();// Save everything

}

DisplaySaveOk();

} catch ( EntityManagerSaveException saveException ) {

ProcessSaveFailure(saveException);

} catch {

throw; // re-throw unexpected exception

}

}

VB

Friend Sub SaveAll()

Try

_em1.SaveChanges() ' Save everything

DisplaySaveOk()

Catch saveException As EntityManagerSaveException

ProcessSaveFailure(saveException)

Catch

Console.WriteLine("While saving, an exception not of type " + _

"EntityManagerSaveException was thrown.")

End Try

End Sub

The serious failure interpretation and recovery work is in the ProcessSaveFailure method which is

custom code that we write. The information we need is in the EntityManagerSaveException instance

passed as a parameter to the method.

SaveChanges() Exceptions

Page 218: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

218 | P a g e

The EntityManager raises a EntityManagerSaveException if the save is canceled (e.g., you cancel it in

your Saving event handler) or if there is any kind of exception.

The EntityServerError handler gets the first crack at the exception. If there is no handler or it doesn’t

handle the exception, the PM throws it again, now in the context of the SaveChanges() call.

We recommend that you do not handle save exceptions in the EntityServerError; leave that to the code near

your SaveChanges() call that catches and interprets save failures.

You‟ll find examples of this recommendation in the Funhouse in the ApplicationController and

EmployeePageController classes.

EntityManagerSaveException

The EntityManagerSaveException inherits from EntityServerException, supplementing that base

class with information pertaining to the save.

That information includes an instance of SaveResult such as would have been returned from

SaveChanges(). We’ll discuss that in a moment. First we’ll get a rough idea of what went wrong by

looking at the exception’s Failure Type.

FailureType Description

Connection The Entity Manager could not reach the data source. There might be a network

connection problem or the data source itself could be down.

Data The data source threw an exception such as a referential integrity violation.

Concurrency There was a concurrency conflict.

Other Could be anything but usually the cause is that the save was canceled by the

Saving event handler. Check the SaveResult.Canceled.

Once we’ve learned the category of failure we can decide how to handle it. We can look to the

precipitating exception itself to further refine our response.

When the failure type is anything but Connection, we’ll likely want to examine the SaveResult to learn

about which entities were affected and how.

SaveResult

Among its contents are:

SaveResult.Canceled which is true if the save was canceled while handling the Saving event.

The precipitating exception, whether from an attempt to connect to the data source or an exception from the

data source itself such as a concurrency conflict or referential integrity violation.

A list of the entities that were not saved called EntitiesWithErrors. In practice, this will always be a

list of one -- the first entity to fail -- since saves are transactional.

Page 219: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

219 | P a g e

These entities remain in the cache and retain exactly the values and setting they had before the save

attempt.

Alternatives to Default SaveChanges Exceptions In prior versions, the PM threw an exception only if there was a problem connecting to or exchanging

data with the database. Database exceptions, such as concurrency violations or referential integrity

violations, were returned to the user in an instance of SaveResult.

There are two problems with that approach:

4. Many developers neglected to check the SaveResult and did not realize that the save had failed.

5. It was difficult to anticipate which kinds of problems would appear in the SaveResult and which would

cause an exception.

Data Source Concurrency A multi-user application must decide how to resolve the conflict when two users try to update the same

data source entity42. Consider the following:

1. I fetch the Employee with Id = 42

2. You fetch the Employee with Id = 42

3. You change and save your copy of the Employee with Id = 42

4. I try to save my copy of the Employee with Id = 42

Is this really going to happen?

There is always a risk that another client or component will change the data source entity while we are

holding our cached copy of it. The risk grows the longer we wait between the time we fetch the entity

and the time we save or refresh it. In offline scenarios, the time between fetch and update can be hours

or days. There could be a great many concurrency conflicts waiting to happen.

If I save my copy now, should it overwrite the one you saved?

If so, we’ve chosen “last-in-wins” concurrency checking. My copy replaces your copy; your changes are

lost.

This is the default in DevForce but we strongly recommend that you adopt another type of concurrency

control. Permitting one user to blithely overwrite changes that another user made can be dangerous or

even fatal.

There is an enormous literature on this subject of “concurrency checking.” The coping strategies are

many and complex.

42 The “data source entity” is the term of convenience we use to describe the data in the data source that map to a corresponding

entity in cache. The data source entity may be a single row in a database table as when an Employee cached entity maps to a

row in an Employee table. Alternatively, the data may be scattered in many places in some other kind of data source. We

have no clue as to the actual location of data behind a Web service entity.

Page 220: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

220 | P a g e

Basic Mechanics of Concurrency Detection

DevForce defers to the ADO.NET Entity Framework’s mechanism for detecting concurrency conflicts at

the time an update is submitted to the back-end data source.

The Entity Framework (EF) permits the developer to designate, in the Entity Model, one or more

columns of a type’s data source table as concurrency columns. When a client application submits an

update order against such a model to the EF, the EF prepares a SQL Update statement. To that

statement it adds a WHERE clause that ensures that all columns designated as a concurrency columns

have the same value they did when the record was last retrieved by the submitting application. (In other

words, they have not been changed in the meantime by another user.) If that proves not to be the case,

the exception thrown by the back-end data source will be propagated back down the application’s

calling chain.

It is the developer’s responsibility to ensure that concurrency columns that should change upon an

update do change. DevForce makes that considerably easier by providing, in the Object Mapper, a

mechanism for automatically updating the value of a given column-based property upon any other

change to the record. The Object Mapper offers six Concurrency Strategies that can be applied to a

given property:

Page 221: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

221 | P a g e

Concurrency Strategy Instruction to DevForce (Action to Perform Whenever the Entity Is Updated)

AutoGuid Replace existing value of the

property with a new GUID value.

AutoDateTime Replace existing value of the

property with the current Date/Time.

AutoIncrement Increment the existing value of the

property by 1.

Server Callback Find, in one of the data source‟s

probe assemblies, a class that

implements the

IConcurrencyStrategy interface,

and call its SetNewConcurrencyValue() method, passing that method the

Entity and the ConcurrencyProperty

as parameters. Said method must be

written to update the

ConcurrencyProperty as appropriate.

Client Just include this column in the

concurrency test. The client

application will take responsibility

for seeing that it is properly updated

whenever the entity is modified.

None Do not use this property to test

concurrency.

Note that some of the strategies only apply to properties of specific types: clearly we cannot force a

GUID value into an integer property, or a DateTime value into a boolean property, and so forth.

It remains the developer’s responsibility to handle any concurrency exception thrown by the back end.

One Concurrency Column, or Many?

Since the Entity Framework permits you to designate any number of columns as concurrency columns, it

may be tempting simply to designate them all.43 That’s one way of making sure that, if anything in the

record has been changed by another user since you got your copy, a concurrency conflict will be

diagnosed.

This may be your only alternative if you have no design access to the database, but be aware that there

will be a performance impact. Every update will be accompanied by a flurry of activity comparing values.

As with other performance issues, you should do some upfront testing to determine whether the

performance impact is unacceptable, or even significant.

If you do have design access to the database, or you’re fortunate enough to inherit a database already

designed the way you want it, it’s generally a better alternative to provide a single column that is

guaranteed to be updated whenever anything else is, and to use that as your sole determinant of a

concurrency conflict. A simple integer column that is incremented each time the record is updated will

43 You can, of course, safely omit the primary key.

Page 222: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

222 | P a g e

do quite nicely; you can also use a GUID, timestamp, or any other type and methodology that

guarantees that the value will change in a non-cyclical way. As you have seen, DevForce makes it easy

for you to make a column auto-updating.

Concurrency and the Object Graph

A large part of the complexity revolves around the scope of concurrency checking. Have I changed an

order if I add, change or delete one of its OrderDetail items? If I change the name of a customer, have I

changed its orders?

These considerations have to do with concurrency control of the business object graph. DevForce does

not support graph concurrency directly. DevForce supports single-table, “business object proper”

concurrency control.

The developer can achieve the desired degree of graph concurrency control by employing single-table

checking within a properly conceived, transactional concurrency plan.

It doesn’t have to be wildly difficult. In brief, the developer adds custom business model code such that

Added, changed, or deleted children entites always modify their parents.

An application save attempt always includes the root entity of the dependency graph.

During a save attempt, the root entity ensures that its children are included in the entity-save list.

These children include their children.

Handling concurrency conflicts in these situations is discussed further in the section “Concurrency and

Dependent Entries.” For now we return to concurrency checking of single business objects.

Pessimistic versus Optimistic Concurrency Checking

There are two popular approaches to concurrency checking: pessimistic and optimistic.

In pessimistic concurrency, we ask the data source to lock the data source entity while we examine it. If

we change it, we write it back to the data source. When we are done looking at it or updating it, we free

the lock. While we have it locked, no one else can see or use it.

This approach holds up other users trying to reach the object we hold. It gets worse if we need many

objects at once. There are potential deadlocks (I grab A, you grab B, I want B too, but can’t get it, so I

wait. You want A also, but can’t get it, so you wait. We both wait forever).

There are more complicated, less draconian implementations to this approach but they amount to the

same punishing performance.

Under optimistic concurrency, we don’t lock the table row. We bet that no one will change the source

data while we’re working with it and confirm our bet when (and if) we try to update the data. The

mechanism works as follows.

We fetch a copy of the table row and turn it into a business object. We work with this copy of the data

source entity. We may decide to update the entity or mark it for deletion. When we save an altered

entity, the business object server converts our intention into a data source management command. That

Page 223: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

223 | P a g e

command, in the process of updating or deleting the supporting table row, confirms that the row still

exists and has not changed since we fetched it. If the row is missing or has changed, the command fails

and it’s up to the application to figure out what to do about it.

Changes are comparatively rare so we have reason to be optimistic that the row will be exactly as we

last found it.

Resolving Concurrency Collisions

Our optimism is usually rewarded. Occasional disappointment is inevitable. Eventually, we will

encounter a conflict between our cached entity, with its pending changes, and the newly-updated data

source entity.

We will want to resolve that conflict one way or the other. The possible resolutions include:

Preserve the pending changes and ask the user what to do.

Abandon the pending changes and re-fetch the entity.

Arrange for the cached entity to become the current entity while preserving the pending changes

Compare the cached entity with the current data source entity and merge the difference per some

business rules or as guided by the user.

The first choice is the easiest place to start. We do nothing with the entity and report the problem to the

user. The cached entity cannot be saved. We leave it up to the user to decide either to abandon the

changes (option #2) or push them forward (options #2 and #3).

The remaining options involve re-fetching the entity from the data source. They differ in what they do

with the entity retrieved – a difference determined by the MergeStrategy44 and how we use it.

C#

aManager.RefetchEntity(anEntity, aMergeStrategy);

VB

aManager.RefetchEntity(anEntity, aMergeStrategy)

OverwriteChanges

The second choice uses the OverwriteChanges strategy to simply discard the user’s changes and update

the entity to reflect the one current in the datasource. While unmatched in simplicity, it is almost the

choice least likely to satisfy the end user. If this is the only option, we should have the courtesy to

explain this to the user before erasing her efforts.

44 We discuss merge strategies in “Advanced Business Object Concepts”.

Page 224: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

224 | P a g e

PreserveChangesUpdateOriginal

The third choice makes the cached entity current by re-fetching with the

PreserveChangesUpdateOriginal strategy. This strategy causes the cached entity to trump the current

datasource entity with a little trickery.

The refetch replaces the cached entity’s original version45 with the values from the current data source

entity but it preserves the cached entity’s current version values, thus retaining its pending changes.

The cached entity’s original concurrency column value now matches the concurrency column value in

the datasource record.

Code Snippet 46. CurrentAndOriginal()

C#

// the current value of the property in the cached entity

Employee.FirstNameEntityProperty.GetValue(anEmployee, EntityVersion.Current);

// the value from the datasource when most recently retrieved

Employee.FirstNameEntityProperty.GetValue(anEmployee, EntityVersion.Original);

VB

' the current value of the property in the cached entity

Employee.FirstNameEntityProperty.GetValue(anEmployee, EntityVersion.Current)

' the value from the datasource when most recently retrieved

Employee.FirstNameEntityProperty.GetValue(anEmployee, EntityVersion.Original)

The effect is as if we had just read the entity from the datasource and applied the user’s changes to it.

If we ask the persistence layer to save it now, the datasource will “think” that we modified the most

recently saved copy of the entity and welcome the changed record.

This option is much like “last one wins” concurrency with a crucial difference: it was no accident. We

detected the concurrency collision and forced the issue in accordance with approved business rules.

The PreserveChangesUpdateOriginal strategy works only if the entity is governed by optimistic

concurrency. If the entity lacks a concurrency column, the refetch uses the OverwriteChanges strategy

instead.

Of course we wouldn‟t be talking about concurrency resolution if there were no concurrency columns.

PreserveChangesUpdateOriginal with Merge

The fourth possibility begins, like the third, with a re-fetch governed by the

PreserveChangesUpdateOriginal strategy. This time we don’t forcibly save the cached entity.

We execute business logic instead which compares the current and original versions, column by column,

deciding whether to keep the locally changed value (the “current” value) or the datasource value (now

tucked inside the “original” value). 45 We cover entity versions in “Advanced Business Object Concepts”.

Page 225: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

225 | P a g e

Such logic can determine if and when the cached entity’s values should prevail. The logic may be entirely

automatic. Alternative, the program could present both versions to the user and let her decide each

difference.

Concurrency and Dependent Entities

What if a bunch of entities are mutually dependent?

Suppose we have an order and its details. User ‘A’ adds two more details and changes the quantity on a

third. She deletes the fourth detail and then saves.

In many applications, an order is never less than the sum of its parts. The order and every one of its

details must be treated as a unit at least for transactional save purposes. We will describe this network

of dependency as a “Dependency Graph”.

DevForce does not offer native support for dependency graphs and its concurrency conflict detection and

resolution features target single entity, “business object proper” concurrency only. We are about to consider

how you can extend DevForce concurrency checking for dependency graphs. We‟ll talk more about

dependency graphs in general later in this section.

Detection

Continuing our story and standing at an Olympian distance with an all knowing eye, we see that User ‘B’

changed the fifth order detail and saved before User ‘A’ tried to save her changes.

User ‘A’ didn’t touch the fifth order detail. She won’t know about the change because there will be no

concurrency conflict to detect; she can’t detect a concurrency conflict unless she save the fifth order

detail and she has no reason to do so.

If this worries you (it worries me), you may want to establish business rules that detect concurrency

violations for any of entity in a dependency graph. A good approach is to

Identify the root entity of the graph (Order) and

Ensure that a change to any node in the graph (OrderDetail) causes the root entity to change.

User ‘B’s change to the fifth detail would have meant a change to the order. User ‘A’s changes also

modified the order. User ‘A’s save attempt will provoke a concurrency violation on the root entity, the

order.

Resolution

Now that User ‘A’ has learned about the violation, what can she do? There is no obvious problem.

Neither ‘A’ nor ‘B’ changed the order entity itself so there are not differences to reconcile. There is only

the tell-tale fact that their concurrency column values are different.

It doesn’t seem proper to proceed blithely, ignoring the violation and proceeding as if nothing

happened. User ‘A’ should suspect something is amiss in the details. The application should re-read all

details, even those the user didn’t change. It should look for diffences at any point in the graph and only

Page 226: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

226 | P a g e

after applying the application-specific resolution rules should it permit the entire order to be saved

again.

What are those resolution rules? We suggest taking the easiest way out if possible: the application

should tell the User ‘A’ about the problem and then throw away her changes.

There must be something fundamentally wrong if two people are changing an order at the same time. In

any case, the complexity of sorting out the conflict and the risk of making a total mess during

“reconciliation” argue for a re-start.

If you can’t take the easy way out – if you have to reconcile – here are a few pointers.

It is probably easiest to use a temporary second EntityManager for the analysis. A single

EntityManager can only hold one instance of an entity at a time and we need to compare two instances

of the same entity. This is manageable if there is only one entity to deal with – we’ve seen how to use

the current and original versions within each entity to carry the difference information.

This trick falls apart when we are reconciling a dependency graph. Instead we’ll put User ‘A’s cached

order and its details in one manager and their dopplegangers from User ‘B’ in another.

The author thinks it is best to import User ‘A’s order and details into the second manager and put User

‘B’s version into the main manager by getting them with the OverwriteChanges strategy. This seems

counter-intuitive but there are a couple of good reasons.

We can ImportEntities into the second manager without logging it in. We‟d have to log in the second

manager before we could use it to get GetEntities. This is not hard, but avoiding it is even easier!

The application probably should favor User „B‟s order; if so that order will be in the main manager where it

belongs.

Show some restraint

The order’s entire object graph is not its dependency graph. The order may consist of details but that

may be as far as it goes.

For example, every detail is associated with a product. If User ‘B’ changed the fifth detail’s product name

or its color, should this provoke a concurrency conflict? It User ‘C’ updated the order’s customer name,

does that mean all orders sold to that customer must be scrutinized for concurrency collisions.

Most businesses will say “no.”

Saving the “Dependency Graph” The DevForce relations between entity classes are indicative of associations among those classes. These

associations define an object graph which may cast a wide net over the data source data.

In this section we consider a portion of that object graph in which a change to one node requires a

change to another node. Such nodes form a sub-graph of mutual dependency we could call a

“dependency graph”. Let’s look a little closer.

Page 227: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

227 | P a g e

Association Types

Associations come in a variety of strengths:

Type Description

Association A simple association is typically read as a “Has a” relationship. An Address has a State or a Part

has a Color.

The two ends have independent lifetimes. A change to the city or the name of the part does not

alter the state or the color.46

Aggregation An aggregation implies a stronger, “Owns a” relationship. A Company owns its employees.

The two ends still have independent lifetimes. There is still a law against slavery and the

employee may transfer to another company. Yet the bond between Company and Employee is

stronger than between Part and Color. There are ramifications to the making and breaking of

ties.

Composition A composition is a “whole / part” relationship in which the whole is said to “consist of” or “be

made up of” the parts. An Order is substantially made up of its detailed items.

This is the strongest bond. The lifetimes of the two ends are closely tied. If the order disappears,

its details disappear with it. Adding, changing, or deleting details alters the parent order.

DevForce itself has no mechanism for distinguishing among these association types. In fact, DevForce

treats its relations as the simplest association. It makes no assumption about the consequences for

related entities of any alterations to either parent or child.

It is not clear that there is a meaningful programmatic distinction between Association and Aggregation.

There will be more business rules surrounding an Aggregation but business rules always require custom

coding so the difference is one of degree rather than of kind.

The relevant fact in this context is that parent and child may be modified independently. Yes, we must

adjust the child if we delete the parent. There may be constraints and consequences to joining and

separating parent and child. There can be side-effects of altering parent or child data unrelated to their

bond. But, in general, we don’t require a modification in one to effect a modification of the other.

Compositions

There are systems that explicitly support the Composition distinction. If you mark a relationship as a

composition, the system will implement it differently. The parts (children) in a composition will be

contained by the whole (parent) and they may only be accessed through the parent.

If you marked an Order’s OrderDetails property as a composition, the only way to obtain details would

be through this property. OrderDetails fetched through any other mechanism would be different objects

than the conceptually same entities fetched with the OrderDetails property.

46 They may become incompatible – as when the change to city moves the address to a different state or the part turns out to be

colorless – but compatibility is a matter for business rules unrelated to the fundamental nature of the association.

Page 228: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

228 | P a g e

We think that is a rare and extreme position which is more trouble than it is worth. The developer can

program to it when it occurs but DevForce does not encourage the practice with any means of its own.

No mechanism is provided to mark the OrderDetails navigation property as a “Composition”.

But this is not to diminish the importance of the Composition bond. In many applications, we should

consider the Order modified if we add, change, or delete one of its OrderDetail entities. If we delete the

Order, we almost certainly intend to delete its details as well.

This is precisely the behavior sought by systems with native support for composition. But we can achieve

the same effect in DevForce. It is not hard work, although it requires some care. The reward is flexibility.

Each application has its own requirements.We can offer only a brief outline of the main points here.

Our application “save” operation concentrates on the root entity (or enties) of the dependency graph.

We implement a Saving handler to invoke composition business rules of the entities.

We add the composition business rules to the business object, wrapped in a method the Saving handler

can call.

We provide for intelligent concurrency resolution to detect and manage the collision of our changes

with changes by other users.

Save the Root Entity

This step is irrelevant if our save operation calls one of the “Save all” methods of the EntityManager.

The “Save all” methods saves every changed entity in the cache.

Our Saving handler must be clever because it might encounter a child entity before its parent. It may not

learn of the parent at all; the child entity will be responsible for modifying its parent and including that

parent in the list of entities to save.

On the other hand, if we choose to save a particular set of entities – the current order and its graph for

example – it may be convenient to compose the “save list” entirely of root entities – orders in this case.

We will see in a moment that compositional business rules ensure that (a) the root entity is in an altered

state and (b) its modified dependent objects are also in the save list.

Saving Event Handler

Remember, the EntityManager raises the Saving event whenever it is ready to write entities to the host

data sources.

We were going to write a Saving handler anyway. We should validate every business object just before

saving it to make sure it is safe to persist. The best approach is to write a Saving event handler that

iterates over the list of entities-to-save (the “save list”), calling a validate method on each.

We might as well extend this approach to call a PrepareSave method instead that both validates and

enforces composition business rules.

Page 229: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

229 | P a g e

Composition Business Rules

We may have any number of composition business rules. One of them must ensure that, if a child is on

the save list, its parent is also on the list. That’s easy because we can always add (and remove) items

from the save list within the Saving event handler.

Another composition rule must ensure that any change to a child entity modifies its parent. That’s

necessary because DevForce will only save an entity that has been changed. It is not sufficient merely to

add the parent to the save list; we must make sure it is in an altered state.

Code Snippet 47. EnforcingConcurrencyCheckingOnAConceptualEntity()

C#

anOrderDetail.UnitPrice *= 1.1M;

Order parentOrder = anOrderDetail.Order;

if (parentOrder.EntityAspect.EntityState == EntityState.Unchanged) {

parentOrder.EntityAspect.SetModified();

}

VB

anOrderDetail.UnitPrice *= 1.1D

Dim parentOrder As Order = anOrderDetail.Order

If parentOrder.EntityAspect.EntityState = EntityState.Unchanged Then

parentOrder.EntityAspect.SetModified()

End If

Concurrency Violations

We always use transactional saves. We’ve taken steps to ensure that all members of the “dependency

graph” – the order and all of its details, for example, - are part of the same save list and are slated for

persistence as a single transaction.

When DevForce persistence layer detects a concurrency violation, it terminates the transaction and

returns the offending entity as we learned earlier. Chances are there will be more than one entity in the

transaction that is in potential concurrency conflict with its corresponding object in the data source.

The end user will be most unhappy if we walk her through each entity one by one. We should resolve

the concurrency conflicts of all entities in the dependency graph in a single shot.

While the exact details will be application specific, they will be some variation on the techniques you

learned for resolving conflicts of individual entities.

Dependency Graph Retrieval Many large DevForce applications use multiple EntityManagers (PM) to maintain separate editing

contexts. They often need to transfer entire entity graphs between PMs.

For example, an application might have a main PM to hold lists of entities. One list might hold SalesReps

and the application could display that list in a grid. Double clicking on one SalesRep row launches a

popup editor for the selected sales rep. Double clicking a different SalesRep row launches a second

popup editor for the rep in that row.

Page 230: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

230 | P a g e

The user can make changes to the first rep, switch to the second editor and make changes to that rep,

go back to the first editor, make more changes, and save them. The second editor (and the rep it edits)

remains open, and its pending changes are not saved. The user may decide to cancel the second editor,

discarding the changes; of course the changes to the first rep have been saved independently.

To implement this scenario, we recommend that each editor have its own PM which constitutes an

“editing context” that is independent both of the main PM and of other editors.

When the application launches an editor, it populates the editor’s PM with the selected SalesRep from

the grid. Because that SalesRep is in the main PM, the application will likely transfer (import) the

selected SalesRep into the editor’s PM. At this moment, the rep in the editor PM is a clone of the rep in

the main PM. After save, the application might export the saved rep back into the main PM where it

now displays in the grid in its post-save glory[1].

Notice that we mentioned only the transfer of a single entity – the “root entity” – between the PMs. In

practice, we often want to transfer both the root entity and many of its related entities. We might

transfer the sales rep and his order information (Orders, OrderDetails) as well so that the entire “entity

graph” can be edited in a single context.

Heretofore, the developer would have to implement the logic to gather up the entities in the graph

before transferring them, a task that could require a sophisticated knowledge of the DevForce Object

Query Language. Now she can use the DevForce “span” technology to compose a single query-like

statement to do the job.

The following example implements the scenario described above:

C#

// Copy selected Employees and their Orders, OrderDetails, and Products

// from one PM to another.

private void GetGraph_OneRootOneSpan() {

DomainModelEntityManager em1 = new DomainModelEntityManager();

DomainModelEntityManager em2 = new DomainModelEntityManager();

int employeeID = 1;

var targetedEmployeesQuery = em1.Employees

.Where(e => e.EmployeeID == employeeID);

int targetedEmployeesCount = targetedEmployeesQuery.Count();

if (targetedEmployeesCount != 1) {

Console.WriteLine("Unable to retrieve Employee with EmployeeID == {0}",

employeeID);

PromptToContinue();

return;

}

// FindEntityGraph() operates against the cache only: it does not retrieve

// entities into the cache. So let's retrieve the desired entities...

Console.WriteLine("Retrieving Emp-Orders-OrderDetails-Products...");

List<Employee> employees = targetedEmployeesQuery

.Include("Orders.OrderDetails.Product").ToList();

Employee anEmployee = employees[0];

Page 231: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

231 | P a g e

// Create roots list and add the employee.

List<Entity> roots = new List<Entity>();

roots.Add(anEmployee);

// Add span(s).

List<EntitySpan> spans = new List<EntitySpan>();

EntitySpan aSpan = new EntitySpan(typeof(Employee),

EntityRelations.FK_Order_Employee,

EntityRelations.FK_OrderDetail_Order,

EntityRelations.FK_OrderDetail_Product);

spans.Add(aSpan);

// Get entity graph for entities in roots

EntityState entityState = EntityState.Unchanged;

IList<Object> entityGraph = em1.FindEntityGraph(roots, spans, entityState);

Console.WriteLine("{0} entities collected by FindEntityGraph.",

entityGraph.Count );

// Import graph into a second EntityManager

em2.ImportEntities(entityGraph, MergeStrategy.OverwriteChanges);

}

VB

Private Sub GetGraph_OneRootOneSpan()

Dim em1 As New DomainModelEntityManager()

Dim em2 As New DomainModelEntityManager()

Dim employeeID As Integer = 1

Dim targetedEmployeesQuery = em1.Employees.Where(Function(e) e.EmployeeID =

employeeID)

Dim targetedEmployeesCount As Integer = targetedEmployeesQuery.Count()

If targetedEmployeesCount <> 1 Then

Console.WriteLine("Unable to retrieve Employee with EmployeeID == {0}",

employeeID)

PromptToContinue()

Return

End If

' FindEntityGraph() operates against the cache only: it does not retrieve

' entities into the cache. So let's retrieve the desired entities...

Console.WriteLine("Retrieving Emp-Orders-OrderDetails-Products...")

Dim employees As List(Of Employee) =

targetedEmployeesQuery.Include("Orders.OrderDetails.Product").ToList()

Dim anEmployee As Employee = employees(0)

' Create roots list and add the employee.

Dim roots As New List(Of Entity)()

roots.Add(anEmployee)

' Add span(s).

Dim spans As New List(Of EntitySpan)()

Dim aSpan As New EntitySpan(GetType(Employee),

EntityRelations.FK_Order_Employee, EntityRelations.FK_OrderDetail_Order,

EntityRelations.FK_OrderDetail_Product)

spans.Add(aSpan)

' Get entity graph for entities in roots

Dim entityState As EntityState = entityState.Unchanged

Dim entityGraph As IList(Of Object) = em1.FindEntityGraph(roots, spans,

entityState)

Console.WriteLine("{0} entities collected by FindEntityGraph.",

entityGraph.Count)

Page 232: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

232 | P a g e

' Import graph into a second EntityManager

Console.WriteLine()

Console.WriteLine("Entities imported to second EntityManager:")

Console.WriteLine("------------------------------------------")

em2.ImportEntities(entityGraph, MergeStrategy.OverwriteChanges)

DisplayCacheContents(em2)

PromptToContinue()

End Sub

Workflow For a Save Let’s put most of these ideas together along with our other knowledge of DevForce business objects and

look at a schematic workflow for saving all pending changes to a single database.

Table 8. Transactional Save Workflow in an N-Tier Deployment

Component Action

Client Tier – Application Code

The client application adds, modifies and deletes any number of business objects

on the client.

The client application asks a EntityManager to save all pending changes.

Client – EntityManager Makes a save list of the new, modified, and deleted entities in cache.

Fires the Saving event. Assume that application listener okays the save.

Connects to the data source and authenticates the user. Assume success.

If there are any temporary ids, the PM sends them to the BOS for fix-up.

Middle Tier – Business Object Server

Builds map of data source-generated ids (e.g., for Identity columns). Calls method

on instance of developer‟s id generation class with remaining temporary. This

method returns a map of temporary-to-permanent ids which the BOS returns to the

client tier.

Client –EntityManager Uses the temp-to-perm id map to replace all temporary ids.

Transmits the save list to the BOS.

Middle Tier – Business Object Server

First the Saving event. This can be used to perform security checks on each entity

in the save list. If a security check fails, an exception can be thrown back to the

EntityManager (or any other desired action taken.) Workflow ends.

Otherwise…

Constructs a batch of insert, update, and delete operations, adjusted for optimistic

concurrency checking as required.

Arranges them by type per the prescribed PersistenceOrder.

Middle Tier – Business Object Server

Forwards them to the Entity Framework for execution.

Data Tier - Data Source Performs the persistence operations. If there are no failures, it commits them; if

there is a single failure, it rolls them all back.

Middle Tier – Business Object Server

If the transaction failed, returns to the EntityManager the identity of the culprit

entity and the exception raised by the data source. The EntityManager stores this

information in the SaveResult and returns to the client application. Workflow ends.

Otherwise…

The transaction succeeded. The BOS re-queries the database(s) for all of the

inserted and modified entities that are sourced in databases, thus capturing the

effects of triggers that fired during save.

Converts the (potentially) revised data into entities and sends them to the client side

EntityManager.

The server‟s local copy of the entities go out of scope and the garbage collector

Page 233: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

233 | P a g e

reclaims them. This enables the object server to stay stateless.

Client Tier – EntityManager

Replaces cashed entities with updates from BOS. They are marked “unchanged”

because they are now current.

Raises the Saved event with list of saved inserted and modified entities.

Client Tier – Application Code

The application resumes.

Saving the Cache to a Local Disk File EntityManager has a property, CacheStateManager, that can be used to capture, save, and restore the

contents of the EntityManager’s cache to a local disk file. The following statements save the contents of

the cache managed by EnttyManager _mgr:

Code Snippet 48. SaveRestoreCacheToDisk()

C#

string cacheFilePath = System.IO.Directory.GetCurrentDirectory() +

"EntityCacheState.bin";

_em1.CacheStateManager.SaveCacheState(cacheFilePath);

VB

Dim cacheFilePath As String = System.IO.Directory.GetCurrentDirectory() &

"EntityCacheState.bin"

_em1.CacheStateManager.SaveCacheState(cacheFilePath)

These statements restore the contents of the EntityCacheState file "C:\_DevForceCache.dat" to the

EntityManager’s current cache:

C#

_em1.CacheStateManager.RestoreCacheState(cacheFilePath);

VB

_em1.CacheStateManager.RestoreCacheState(cacheFilePath)

When called using the overload above, the restore operation using RestoreStrategy.Normal. That

RestoreStrategy restores the data in the cache state file using a MergeStrategy of PreserveChanges (see

the discussion of this elsewhere in this chapter); it also restores the DefaultSaveOptions and

DefaultQueryStrategy saved in that file, overwriting the current values for those properties.

When RestoreStrategy.Normal doesn’t meet your needs, you can restore using a custom

RestoreStrategy:

C#

bool restoreSaveOptions = true;

bool restoreQueryStrategy = false;

RestoreStrategy aRestoreStrategy = new RestoreStrategy(

restoreSaveOptions, restoreQueryStrategy, MergeStrategy.OverwriteChanges);

_em1.CacheStateManager.RestoreCacheState(cacheFilePath, aRestoreStrategy);

VB

Dim restoreSaveOptions As Boolean = True

Dim restoreQueryStrategy As Boolean = False

Page 234: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

234 | P a g e

Dim aRestoreStrategy As New RestoreStrategy(restoreSaveOptions, restoreQueryStrategy,

MergeStrategy.OverwriteChanges)

_em1.CacheStateManager.RestoreCacheState(cacheFilePath, aRestoreStrategy)

CacheStateManager also includes a method, GetCacheState(), which returns the state of the cache as a

serializable in-memory object:

C#

EntityCacheState cacheState = _em1.CacheStateManager.GetCacheState();

VB

Dim cacheState As EntityCacheState = _em1.CacheStateManager.GetCacheState()

This can be used in a variety of ways; for example, in a server-side method called from the client using

EntityManager.InvokeServerMethod() or InvokeServerMethodAsync(), you could fill an EntityManager’s

cache with any arbitrary collection of data, capture that in an EntityCacheState, and return that

EntityCacheState to the client where it could be restored using another overload of RestoreCacheState:

C#

_em1.CacheStateManager.RestoreCacheState(cacheState);

VB

_em1.CacheStateManager.RestoreCacheState(cacheState)

You can also, of course, encrypt the cache state before saving it to local storage.

XML Serialization of Business Objects IdeaBlade entities can be serialized as XML for any number of purposes, including exposing these

entities to a Web Service, or as the first step in some XSLT transform for reporting or further processing.

Please see Microsoft’s WCF documentation for detailed examples of exposing objects to Web Services

via data contract serialization.

All entities generated by DevForce are marked with WCF DataContract attributes; and all public

properties of these entities are marked with a DataMember attribute. Serialization using standard WCF

via either the

System.Runtime.Serialization.DataContractSerializer, or the

System.Runtime.Serialization.NetDataContractSerializer

is supported.

One of the big issues with XML Serialization when serializing object graphs (objects that are connected

to other objects, ad-infinitum) has to do with the depth of the object graph that should be serialized.

Page 235: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

235 | P a g e

Without some mechanism to control the depth of the graph, the serialization of a single entity might

result in hundreds or even thousands of related entities being serialized.

DevForce controls this by only serializing entities that are present within an EntityManager’s cache at

the inception of serialization and are navigable from the directly serialized entities. (Think of this as all

entities that are available via a CacheOnly query) This allows fine-grained control over what will be

serialized. Any relation properties that would return an entity or entities that are not in the cache will

instead serialize the property value either as a null entity (for scalar properties), or as an empty

collection (for collection properties).

The examples serializes two employees along with all related Orders and their line items (OrderlDetails):

Code Snippet 49. SerializeBusObjects()

C#

private void SerializeBusObjects(string serializerName) {

var employees = _em1.Employees

.OrderBy(e => e.LastName).Take(2).Include("Orders.OrderDetails").ToList();

var aMemoryStream = new MemoryStream();

var aXmlDictionaryWriter =

System.Xml.XmlDictionaryWriter.CreateTextWriter(aMemoryStream);

if (serializerName == "NetDataContactSerializer") {

SerializeWithNetDataContractSerializer(employees, aXmlDictionaryWriter);

}

else {

SerializeWithDataContractSerializer(employees, aXmlDictionaryWriter);

}

aXmlDictionaryWriter.Flush();

aMemoryStream.Position = 0;

string result = StreamFns.ToString(aMemoryStream);

}

private static void SerializeWithNetDataContractSerializer(List<Employee> employees,

System.Xml.XmlDictionaryWriter aXmlDictionaryWriter) {

NetDataContractSerializer aNetDataContractSerializer = new NetDataContractSerializer();

aNetDataContractSerializer.WriteObject(aXmlDictionaryWriter, employees);

}

private static void SerializeWithDataContractSerializer(List<Employee> employees,

System.Xml.XmlDictionaryWriter aXmlDictionaryWriter) {

DataContractSerializer aDataContractSerializer = new DataContractSerializer(

typeof(Employee), new Type[] {

typeof(List<Employee>) }, int.MaxValue, false, true, null);

aDataContractSerializer.WriteObject(aXmlDictionaryWriter, employees);

}

VB

''' <summary>

''' DataContract serialization

''' </summary>

Private Sub SerializeBusObjects(ByVal serializerName As String)

Dim employees = _em1.Employees.OrderBy(Function(e)

e.LastName).Take(2).Include("Orders.OrderDetails").ToList()

Dim aMemoryStream = New MemoryStream()

Dim aXmlDictionaryWriter =

System.Xml.XmlDictionaryWriter.CreateTextWriter(aMemoryStream)

If serializerName = "NetDataContactSerializer" Then

SerializeWithNetDataContractSerializer(employees, aXmlDictionaryWriter)

Else

SerializeWithDataContractSerializer(employees, aXmlDictionaryWriter)

End If

Page 236: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Persistence

236 | P a g e

aXmlDictionaryWriter.Flush()

aMemoryStream.Position = 0

Dim result As String = StreamFns.ToString(aMemoryStream)

Console.WriteLine("Two employees, serialized using the {0}..." & vbLf, serializerName)

Dim abbreviatedResult As String = result.Substring(0, 500) & vbLf & vbLf &

"...[snip]..." & vbLf & vbLf & result.Substring(result.Length - 201)

Console.WriteLine(abbreviatedResult)

PromptToContinue()

End Sub

Private Sub SerializeWithNetDataContractSerializer(ByVal employees As List(Of Employee),

ByVal aXmlDictionaryWriter As System.Xml.XmlDictionaryWriter)

Dim aNetDataContractSerializer As New NetDataContractSerializer()

aNetDataContractSerializer.WriteObject(aXmlDictionaryWriter, employees)

End Sub

Private Sub SerializeWithDataContractSerializer(ByVal employees As List(Of Employee),

ByVal aXmlDictionaryWriter As System.Xml.XmlDictionaryWriter)

Dim aDataContractSerializer As New DataContractSerializer(GetType(Employee), New Type()

{GetType(List(Of Employee))}, Integer.MaxValue, False, True, Nothing)

aDataContractSerializer.WriteObject(aXmlDictionaryWriter, employees)

End Sub

Page 237: Dev Force 2010 Developers Guide

237 | P a g e

Business Object Persistence – Advanced

Page 238: Dev Force 2010 Developers Guide

238 | P a g e

Business Object Persistence – Advanced .................................................................................. 237

State of the Release Candidate Documentation ................................................................................................ 239

Getting Information About an Entity Type with GetEntityMeta() ................................................................. 239

Access Both Local and Remote Data Sources In the Same N-tier Application ............................................. 240

Stored Procedure Queries ................................................................................................................................... 241

SQL Server Stored Procedure Queries .............................................................................................................. 243

Stored Procedure Entity Navigation .................................................................................................................. 246

Forced Re-fetch .................................................................................................................................................... 246

Lost Connection During Query .......................................................................................................................... 248

Query Cache ......................................................................................................................................................... 249

EntityManager.RemoveEntities Overload Preserves Query Cache ................................................................... 249

MergeStrategy In More Detail............................................................................................................................ 251

The EntityManager.AttachEntity Method ........................................................................................................ 255

Filtering Queries .................................................................................................................................................. 258

Query Inversion in More Detail ......................................................................................................................... 260

Transactional Queries ......................................................................................................................................... 265

DevForce and Data Sources – Deep Dive ........................................................................................................... 266

DataSourceKeys, DataSourceKeyResolvers, and DataSourceExtensions ......................................................... 267

EntityManagers and DataSourceExtensions ...................................................................................................... 268

Tenant Extensions .............................................................................................................................................. 271

Multi-Part Extensions ........................................................................................................................................ 271

Extensions and EntityServers ............................................................................................................................ 272

Dynamic DataSourceKeys and the DataSourceKeyResolver ............................................................................ 273

Multiple Application Environments ................................................................................................................... 275

Multiple EntityManager Instances ..................................................................................................................... 276

Multi-Threading in a DevForce App ................................................................................................................. 277

Batching Asynchronous Tasks ............................................................................................................................ 279

Service Oriented Architecture ............................................................................................................................ 281

POCO Support in DevForce ............................................................................................................................... 283

Examples of POCO Classes ............................................................................................................................... 284

Examples of a POCO Service Provider Class .................................................................................................... 285

Example of a Client-Side Class Containing Extension Methods for the EntityManager ................................... 288

Obtaining an EntityAspect Property on Your POCO Object ............................................................................. 289

Page 239: Dev Force 2010 Developers Guide

239 | P a g e

Data Contract Serializer (DCS) versus .NET Data Contract Serializer (NDCS) .............................................. 290

POCO Save mechanisms ................................................................................................................................... 293

Summary – Things to Remember When Using POCOs in Your DevForce App .............................................. 298

State of the Release Candidate Documentation

We are working hard to update all of our documentation from DevForce 2009 to DevForce 2010, .NET 4,

and Silverlight 4. During this conversion, you may find some sections that are out of date, but you should

be able to get many of the examples to work, with small modifications, by checking against the API

Documentation for the current method signatures.

Getting Information About an Entity Type with GetEntityMeta()

The instance method GetEntityMetadata() on the EntityMetadataStore type returns an EntityMetadata

object that is rich with information about a specified entity type:

C#

EntityMetadata employeeEntityMetaData =

EntityMetadataStore.Instance.GetEntityMetadata(typeof(DomainModel.Employee));

VB

The EntityMetadata objects provides the following members:

Page 240: Dev Force 2010 Developers Guide

240 | P a g e

The table below provides an explanation for key members:

Property CanQueryByEntityKey Gets whether primary key queries are allowed.

Property ComplexTypeProperties Returns a collection of EntityProperties that describe complex object

properties for entities of this type.

Property ConcurrencyProperties Returns a collection of EntityProperties that are concurrency properties

for entities of this type.

Method CreateEntity() Creates a new entity of the type described by this metadata item.

Property DataEntityProperties Returns a collection of DataEntityProperties for entities of this type.

Property DataSourceKeyName Gets the name of the Data Source Key associated with this type.

Property DefaultEntitySetName Gets the default EntitySetName for entities of this type.

Property EntityProperties Returns a collection of EntityProperties that belong to entities of this

type.

Property EntityType Gets the type of the entity.

Property KeyProperties Returns a collection of EntityProperties that are keys for entities of this

type.

Property NavigationProperties Returns a collection of NavigationEntityProperties for entities of this

type.

Such metadata can be useful in many situations. For example, suppose you wish to dynamically populate

a form with bound controls for the properties of a type. You could easily get the list you need from the

EntityMetadataStore.

Access Both Local and Remote Data Sources

In the Same N-tier Application

An application may need to persist volatile data to a centrally hosted database and have the ability to

simultaneously access comparatively static data on a local database.

Field technicians who service complex machine parts may need ready access to voluminous parts

catalogs and repair manuals. The catalog and repair data don’t change often . They may be stored in a

database on the tech’s laptop.

On the other hand, the central office needs to monitor the technicians rounds and dispatch him to new

client sites. There could be significant exchange of information between the dispatch center and the

remote technician.

A DevForce program should be able to provide access to both the remote and local database in an n-tier

deployed application.

One of the EntityManager constructors facilitates construction of such an application.

Page 241: Dev Force 2010 Developers Guide

241 | P a g e

C#

public EntityManager(

bool pShouldConnect,

String pDataSourceExtension,

PersistenceServiceOption pPersistenceServiceOption)

VB

The caller sets the third parameter to the value of a PersistenceServiceOption enumeration that

indicates how the how the EntityManager’s PersistenceService should be configured.

C#

public enum PersistenceServiceOption {

/// <summary>

/// Use the Ibconfig file [remoting][remotePersistenceEnabled] node.

/// </summary>

UseDefaultService = 0,

/// <summary>

/// Use a local service - Service will run in process with the client

/// The Ibconfig file [remoting][remotePersistenceEnabled] node is ignored.

/// </summary>

UseLocalService = 1,

/// <summary>

/// Use a remote service as defined in the Ibconfig file [remoting] node.

/// The Ibconfig file [remoting][remotePersistenceEnabled] node is ignored.

/// </summary>

UseRemoteService = 2

}

VB

After configuration, the EntityManager will connect either to the remote database or to the local

database. A specific PM cannot switch between the two modes. But the application can have more than

one EntityManager and bridge the two at convenient moments – which is exactly how we’d approach

the scenario described above.

Stored Procedure Queries

Stored procedure queries are not available in DevForce 2010 RC1.

We broached the subject earlier of the occasional need to use a stored procedure to query for business

objects. The need arises most frequently when we require the entities resulting from an extraordinarily

complex query involving large volumes of intermediate data that are not themselves required on the

client.

One might imagine a multi-step query that touched several tables, performed multi-way joins, ordered

and aggregated the intermediate results, and compared values with many thousands of records, all so as

to return a handful of qualifying results. All of the other data were needed only to satisfy the query; the

user won’t see any of them and there is no point to transmitting them to the client.

Page 242: Dev Force 2010 Developers Guide

242 | P a g e

This is a clear case for a stored procedure because we can and should maximize performance by

performing all operations as close to the data source as possible.

Chances are that the entities returned by the stored procedure are entities we already know. That

procedure could be just an especially resource-consuming query for Order entities that we retrieve and

save in the usual way under normal circumstances.

The Stored Procedure Query is perfect for this situation. We define such a query, identify Order as the

query return type, and turn it loose on the database. We accept the sproc-selected Order objects and

work with them in our typical merry way.

Note that a stored procedure query, by its nature, must be executed by the database: we can’t run it

against the entity cache47. So we may not invoke it while the application is running offline.

Accessing Related Entities Via Navigation Properties

On Entities Retrieved Using Stored Procedure Queries When using a stored procedure query, the Entity Framework handles the retrieval of information about related

entities differently than it does for normal queries. In the normal case, foreign key values are retrieved and retained

with the returned entities. These foreign key values are not exposed as public properties on the returned entities, but

they‟re present under the covers. For entities retrieved via stored procedures, the EF does not have sufficient

information reliably to identify foreign keys, and so does not retrieve values for any.

Recall that in the Entity Framework – in contrast to the behavior DevForce -- all related entities must be retrieved by

explicit command. When such command is given, EF always returns the related entities. But for parent entities that

were retrieved using stored procedures, it necessarily uses a different (and less performant) process to get the related

entities than for entities retrieved using ordinary queries. That is made necessary by the lack of foreign key values

on the parent entities.

DevForce, in contrast to the EF, retrieves related entities automatically; all you need do is to make

reference to them. However, in the case of entities retrieved via stored procedure queries, we had to

make a tough call. One choice was to retrieve foreign key values automatically during any stored

procedure query. That would produce the simplest and most intuitive behavior on the client: for all

entities, retrieval of related entities referenced through navigation properties would be automatic.

But of course there was a problem: each foreign key requires an additional round trip to the database from the object

server; and there is, of course, a performance price for this.

We elected to make the default the more performant choice: unless you ask for them explicitly, we do not retrieve the

foreign key values during stored procedure queries. In consequence, by default, references to navigation properties

on such entities will return Null Entities.

If you know you will need the related entities for entities retrieved using a stored procedure proc, you can get them

via the ShouldLoadEntityRefs property on the StoredProcQuery. If you set this property to true – the default is false

-- all foreign key properties on the entity are looked up during the initial query, and references to related entities will

return the proper entities.

47 There is an advanced technique for applying a stored procedure query to the cache that we cover briefly in “Advanced

Business Object Concepts.”

Page 243: Dev Force 2010 Developers Guide

243 | P a g e

SQL Server Stored Procedure Queries Suppose your data source table includes a stored procedure named “SalesByYear”. It is defined as

follows:

TSQL

ALTER procedure "SalesbyYear"

@Beginning_Date DateTime, @Ending_Date DateTime AS

SELECT OrderSummary.ShippedDate, OrderSummary.id, "Order Subtotals".Subtotal,

DATENAME(yy,ShippedDate) AS Year

FROM OrderSummary INNER JOIN "Order Subtotals" ON OrderSummary.Id = "Order

Subtotals".OrderSummaryId

WHERE OrderSummary.ShippedDate Between @Beginning_Date And @Ending_Date

When included among the items imported into an Entity Data Model, this results in the following

Function element in the schema (SSDL) section of the Entity Model file:

XML

<Function Name="SalesbyYear" Schema="dbo" Aggregate="false" BuiltIn="false"

NiladicFunction="false" IsComposable="false"

ParameterTypeSemantics="AllowImplicitConversion">

<Parameter Name="Beginning_Date" Type="datetime" Mode="In" />

<Parameter Name="Ending_Date" Type="datetime" Mode="In" />

</Function>

To make this convenient available for calling directly off of our EntityManager (as you would equally

have to do to make it available on the ADO.NET ObjectContext), you must add a FunctionImport

element in the conceptual model (CSDL) section of the Entity Model:

XML

<FunctionImport Name="GetSalesByYear" EntitySet="SalesByYearResults"

ReturnType="Collection(IdeaBladeTest1Model.EF.SalesbyYear)">

<Parameter Name="Beginning_Date" Type="DateTime" Mode="In" />

<Parameter Name="Ending_Date" Type="DateTime" Mode="In" />

</FunctionImport>

This will cause a C# or VB method to be generated in your EntityManager class by the name you

specified, “GetSalesByYear”. Note that the FunctionImport element also specifies the EntitySet into

which results returned by the stored proc will be housed: “SalesByYearResults”; and the return type of

the method, which will be a collection of SalesByYear entities.

The SalesByYear Entity type must be defined in your conceptual model:

Page 244: Dev Force 2010 Developers Guide

244 | P a g e

XML

<EntityType Name="SalesbyYear" Abstract="false" ib:PrevName="SalesbyYear">

<Key>

<PropertyRef Name="ShippedDate" />

</Key>

<Property Name="ShippedDate" Type="DateTime" Nullable="false" />

<Property Name="id" Type="Int64" Nullable="false" />

<Property Name="Subtotal" Type="Decimal" Nullable="false" Precision="19"

Scale="4" />

<Property Name="Year" Type="String" Nullable="false" MaxLength="4" />

</EntityType>

The method specified in the conceptual model in the FunctionImport element must be mapped to the

Function element in the SSDL that represents the stored procedure. That mapping must, of course, be

specified in the mapping (MSL) section of the Entity Model:

XML

<FunctionImportMapping FunctionImportName="GetSalesByYear"

FunctionName="IdeaBladeTest1Model.EF.Store.SalesbyYear" />

Having done all of that in your Entity Model, you can now use the resultant method as shown following

two examples:

C#

_em1 = new IdeaBladeTest1Entities();

[TestMethod]

public void StoredProcQuery() {

DateTime dt1 = DateTime.Parse("1/1/1990");

DateTime dt2 = DateTime.Parse("1/1/2000");

var results = _em1.GetSalesByYear(dt1, dt2);

}

[TestMethod]

public void StoredProcQuery2() {

DateTime dt1 = DateTime.Parse("1/1/1995");

DateTime dt2 = DateTime.Parse("12/31/1996");

var results = _em1.GetSalesByYear(dt1, dt2).Where(s => s.Subtotal > 2500);

}

VB

The method is simply called on the EntityManager with appropriate parameters. It returns an

IEnumerable<SalesByYear>, which can be subjected to qualifying filters as you see in the second

example above.

Below is the Generated code in the domain model designer code file for the GetSalesByYear() method:

Page 245: Dev Force 2010 Developers Guide

245 | P a g e

C#

#region GetSalesByYear StoredProcQuery

/// <summary>

/// Constructs and executes the <see

cref="T:IdeaBlade.EntityModel.StoredProcQuery"/>

/// associated with the given stored procedure.

/// </summary>

public IEnumerable<IdeaBladeTest1Model.SalesbyYear> GetSalesByYear(

Nullable<DateTime> Beginning_Date, Nullable<DateTime> Ending_Date) {

StoredProcQuery query = GetSalesByYearQuery(Beginning_Date, Ending_Date);

return this.ExecuteQuery<IdeaBladeTest1Model.SalesbyYear>(query);

}

/// <summary>

/// Constructs and returns the <see

cref="T:IdeaBlade.EntityModel.StoredProcQuery"/>

/// associated with the given stored procedure.

/// </summary>

public StoredProcQuery GetSalesByYearQuery(

Nullable<DateTime> Beginning_Date, Nullable<DateTime> Ending_Date) {

QueryParameter Beginning_DateParameter;

if (Beginning_Date.HasValue) {

Beginning_DateParameter = new QueryParameter("Beginning_Date", Beginning_Date);

} else {

Beginning_DateParameter = new QueryParameter("Beginning_Date",

typeof(DateTime));

}

QueryParameter Ending_DateParameter;

if (Ending_Date.HasValue) {

Ending_DateParameter = new QueryParameter("Ending_Date", Ending_Date);

} else {

Ending_DateParameter = new QueryParameter("Ending_Date", typeof(DateTime));

}

StoredProcQuery query =

new StoredProcQuery(typeof(IdeaBladeTest1Model.SalesbyYear),

"GetSalesByYear",

Beginning_DateParameter,

Ending_DateParameter);

return query;

}

#endregion GetSalesByYear StoredProcQuery

VB

For the record, here’s an alternative way to invoke your stored procedure:

C#

[TestMethod]

public void StoredProcQuery3() {

DateTime dt1 = DateTime.Parse("1/1/1996");

DateTime dt2 = DateTime.Parse("12/31/1998");

StoredProcQuery query = new StoredProcQuery(typeof(SalesbyYear));

// Note that a FunctionImport must be defined in the Entity Model

query.ProcedureName = "GetSalesByYear";

query.Parameters.Add(new QueryParameter("Beginning_Date", dt1));

query.Parameters.Add(new QueryParameter("Ending_Date", dt2));

var results = _em1.ExecuteQuery<SalesbyYear>(query);

}

VB

Page 246: Dev Force 2010 Developers Guide

246 | P a g e

Stored Procedure Entity Navigation

Dot Navigation is a bit tricky for business objects that are defined by a stored procedure (sproc entities).

If the source class is a sproc entity, the tool can implement the Source.Target navigation property if

the target class is a table or view entity.

Unfortunately, there is no obvious way to automatically generate the implementation if the target is

also a sproc entity. Consider an example.

Suppose the source is Customer and the target is Order and both are mapped to stored procedures. In

principle we could map the Customer to Order by creating a relation that joins Order.CustomerId to

Customer.Id48. We tell the tool “implement this!”

Unfortunately, the Object Mapper must give up immediately. The tool knows the signature of the base

stored procedure but has no idea how the sproc actually responds to different parameter values.

Therefore, it can not invoke the Order’s underlying stored procedure such that the sproc returns all

orders for a given customer. That operation may not even be possible.

A developer can interpret the stored procedure well enough to know what call (if any) would do the job.

Accordingly, the developer may choose to implement a Customer.Orders property within the custom

logic of the Customer class, using a stored procedure query.

The same conundrum confronts us when we devise a relation heading the other direction, from any business object entity to a stored

procedure entity. Once again, the Object Mapper does not know how to call the stored procedure so that it returns the objects expected

by the source entity type.

Table 9 summarizes the situation.

Table 9. Who writes the navigation property involving a sproc entity.

Navigation property Relation Written By

Source Entity Type Target Entity Type Tool Developer

Sproc Table or View

Sproc Sproc or Web Service

Any type Sproc

Forced Re-fetch

There are a number of methods that help us re-fetch specific entities from their data sources. Among

them are EntityList.ForceRefetch and EntityManger.RefetchEntities<T>. They assume the

OverwriteChanges merge strategy but we can give them any of the other merge strategies.

48 In fact you can‟t do this within the Object Mapper for reasons we are now discussing.

Page 247: Dev Force 2010 Developers Guide

247 | P a g e

OverwriteChanges replaces the cached entities, overwriting our pending changes. We often want to (a)

keep pending changes but (b) refresh copies of unmodified entities.

The PreserveChanges… strategies can help us achieve our purpose.

Table 10. PreserveChanges… strategies in a forced re-fetch

Strategy Description

PreserveChanges Replace unchanged entities but keep changed entities as they are.

PreserveChangesUnless OriginalObsolete

Replace unchanged entities and changed entities that are obsolete (i.e., that

would fail an optimistic concurrency check if saved now).

PreserveChangesUpdateOriginal Replace unchanged entities. Keep changed entities and make them current

if they are obsolete by updating their original versions.

Custom Navigation property with Forced Re-fetch

Navigation properties execute according to strategy prescribed by the EntityManager.

DefaultQueryStrategy. The default is Normal. We can change it dynamically but the Normal strategy is

the best default choice for most applications so let’s assume we leave it that way.

The first time we call the navigation property the PM will get the entities from the data source and put

them in the cache. The next time, and every subsequent time, the navigation property will look in the

cache first and find the entities there. So during the entire user session these entities may never be

refreshed.

This is great for a list of states but not so great for more volatile entities such as theater seats.

Some developers will be tempted to override the navigation property to get fresh data from the data

source every time. The following is a typical example that strives to keep the Customer.Orders ultra-

current:

C#

public override ReadOnlyEntityList<Order> Orders {

get {return base.Orders.

ForceRefetch(MergeStrategy.Overwrite);}

}

VB

Public Overrides ReadOnly Property Orders() As _

IdeaBlade.Persistence.ReadOnlyEntityList(Of Order)

Get

Return MyBase.Orders.ForceRefetch(MergeStrategy.Overwrite)

End Get

End Property

Page 248: Dev Force 2010 Developers Guide

248 | P a g e

Performance is likely to be terrible. Entity properties fire frequently and sometimes unexpectedly.

Properties should return quickly. This one goes to the data source every time. Not good.

The intention is laudable and we can make this work. One approach is to remember the last time we

invoked this method. If we just did it, return with the most recently fetched list. If we did it “too long

ago”, force the re-fetch.

Lost Connection During Query

What if the EntityManager can’t reach the data source when processing a query49 either because of a

network connection problem or because the data source is unavailable?

This is a non-issue for the CacheOnly query but applies to all other fetch strategies. The

PersisenceManager responds differently depending upon whether or not it knows that the connection

is broken before attempting the query.

If it knows it is disconnected, its behavior is simple: treat every query as a CacheOnly query. This is

consistent with the general principle that writing code for a disconnected application should be as easy

as possible. We shouldn’t have to write a lot of special case logic once we have acknowledged that the

application is off-line.

Unexpected loss of connection

When the EntityManager believes it is connected, it will attempt to search the database once the cache

proves inadequate. If in fact it is not connected or the connection is broken during the search, the

EntityManager will and then raise an event. If the application doesn’t handle the event, it throws an

exception.

If the EntityManager “believes” it is connected and discovers that it can’t reach the data source while

processing the query, it will take the following steps in sequence.

6. change its internal state to “disconnected”

7. raise An EntityServerError event

8. throw An EntityServerException unless the event says it handled the problem.

We can and should supply the EntityManager with An EntityServerError event handler. Our handler

can quickly tell that the cause is a connection problem. It can distinguish between network connection

failure and data source unavailability.

If we know what to do, we can do it and signal that we’ve handled it; the EntityManager won’t throw

an exception. If we don’t handle the event or don’t signal that we’ve handled it, the EntityManager will

throw An EntityServerErrorException.

49 This analysis applies to both entity query and entity navigation.

Page 249: Dev Force 2010 Developers Guide

249 | P a g e

Query Cache

DevForce caches queries to improve performance50. Consider a query for employees with FirstName =

“Nancy”. The QueryStrategy is Normal which means the fetch strategy is CacheThenDataSource.

When we execute this query in an empty EntityManager, there will be a trip across the network to

fetch the entities from the data source. We get back “Nancy Davolio” and “Nancy Sinatra”. If we execute

the query again, the EntityManager satisfies the query from the entity cache and returns the same

result; it does not seek data from the data source.

During the first run the EntityManager stored the query in its Query Cache51. The second time it found

the query in the Query Cache and thus knew it could use apply the cache to the query instead.

If we change “Nancy” to “Sue” and run the query again, we get back just “Nancy Sinatra”. If we change

“Sally Wilson” to “Nancy Wilson” and run it again, we’ll get the principals of a strange duet. So far,

everything is working fine.

Meanwhile, another user saves “Nancy Ajram” to the data source. We run our query again and … we still

have just a duet. The EntityManager didn’t go to the data source so it doesn’t find the Lebanese pop

star.

Such behavior may be just fine for this application. If it is not, the developer has choices. She can:

use a QueryStrategy with a different fetch strategy that looks at the database first.

clear the query cache explicitly by calling EntityManager.ClearQueryCache

clear the query cache implicitly by removing any entity from the entity cache

EntityManager.RemoveEntities Overload Preserves Query Cache When we remove an entity from a EntityManager’s entity cache, DevForce automatically clears the PM’s

entire query cache. That’s right – it erases the EntityManager’s memory of all the queries it has

performed.

Suppose we frequently query for employees hired this year. If we issue this query twice. The first query

fetches the employees from the database; the second retrieves them from the cache. The second query

is almost instantaneous.

Then we remove an unrelated entity such as a Customer or an Address. We query again. Instead of

reading from the cache as it did before, the PM goes back to the database for these employees.

Seems unfair, doesn’t it? But it’s the safe thing to do.

50 This analysis applies to both entity queries and entity navigation. Both use CacheFirstThenDataSource fetch strategy

by default.

51 The PersistenceManager stores the query in the query cache when (a) the query is successful and (b) it searched the data

source (not just the cache).

Page 250: Dev Force 2010 Developers Guide

250 | P a g e

If we issue the same query multiple times, we expect the same results every time. We expect a different

result only if data relevant to our query have changed.

The EntityManager will search the local cache instead of the database only if it “believes’ that all

essential information necessary to perform the query are resident in the cache. If it “thinks” that the

cache has been compromised, it should go back to the data source to satisfy the query.

Removing an entity compromises the cache. For sure it invalidates at least one query – the query that

fetched it in the first place. But is that the only invalidated query? The EntityManager does not know. So

it does the safe thing and forgets all queries.

You and I know (or we think we know) that removing a Customer or Address has no bearing on

employees hired this year. The EntityManager is not so sure.

There are circumstances when (a) we have to remove an entity and (b) we are certain that no queries

will be adversely affected. For example, our query may return entities which we’ve marked as inactive.

We never want inactive entities in our cache but, for reasons we need not explain here, we have inactive

entities in the cache.

We want to remove those entities. Being inactive they cannot possibly contribute to a correct query

result.

Unfortunately, removing those entities clears the entire query cache. The EntityManager will satisfy

future queries from the database until it has rebuild its query cache.

This is not a problem if we rarely have to purge inactive entities. But what if we have to purge them

after almost every query52? We will never have a query cache and we will always search the database.

The performance of our application will degrade

Fortunately, there is now a RemoveEntities signature that can remove entities without clearing the

query cache. In the full knowledge of the risk involved, we can call

EntityManager.RemoveEntities(entitieToRemove, false)

The “false” parameter tells the PM that is should not clear the query cache.

Remember: removing an entity and deleting it are different operations. Removing it from the cache erases

it from client memory; it says nothing about whether or not the entity should be deleted from its permanent

home in remote storage. “Delete”, on the other hand, is a command to expunge the entity from permanent

storage. The “deleted” entity stays in cache until the program can erase it from permanent storage.

52 This is not a rare scenario.

Page 251: Dev Force 2010 Developers Guide

251 | P a g e

MergeStrategy In More Detail

The discussion here expands upon that in the section ”Inversion Mode” earlier in the basic topic

document for Business Object Persistence. It is provided as a supplement for a deeper understanding of

the topic.

What happens during the merge of a data source entity and a cached entity depends upon the answers

to three crucial questions:

1. Is the entity current or obsolete?

2. How has it changed?

3. Is the entity represented in the data source?

Is the entity current or obsolete relative to the data source?

We compare the cached entity’s concurrency column property value to that of its data source entity. If

the two are the same, the cached entity is current; if they differ, the cached entity is obsolete.

As it happens, the cached entity has two concurrency column property values, a current one and an

original one. The value of the concurrency column in the current version is meaningless. It’s the value of

the concurrency column in the original version that counts.

Every DevForce entity has an original version and a current version of its persistent state. We can get to

one or the other by means of a static GetValue() method defined on the EntityProperty class. For

example, the following code gets the original value (as retrieved from the database) for the

RequiredDate property of a particular Order instance:

C#

DomainModelEntityManager mgr = DomainModelEntityManager.DefaultManager;

anOrder = mgr.Orders.Where(o => o.OrderID == 10248);

Datetime reqdDate =

Order.RequiredDateEntityProperty.GetValue(anOrder, EntityVersion.Original);

VB

Both of the following statements get the current value for the same property:

C#

reqdDate =

Order.RequiredDateEntityProperty.GetValue(anOrder, EntityVersion.Current);

reqdDate = anOrder.RequiredDate; // same as above (but simpler!)

VB

Again, DevForce and the Entity Framework determine if our cached entity is current or obsolete based

on the original version of the property value.

Page 252: Dev Force 2010 Developers Guide

252 | P a g e

How has it changed?

The merge action depends upon whether the entity was added, deleted, or changed since we set its

original version. The entity’s EntityState property53 tells us if and how it has changed.

Is the entity represented in the data source?

If there is a data source entity that corresponds to the cached entity, we may use the data from data

source entity to change the cached entity in some way.

If we don’t find a matching data source entity, we have to decide what to do with the cached entity.

Maybe someone deleted the data source entity in which case we might want to discard the cached

entity. If we, on the other hand, we want to save the cached entity, we’ll have to insert it into the data

source rather than update the data source.

Merging when the entity is in the data source

We’ll look at each strategy and describe the outcome based on (a) whether or not the cached entity is

current and (b) the entity’s EntityState.

If the entity is Unchanged, we always replace both its original and current versions with data from the

data source entity.

Our remaining choices are evident in the following table.

Table 11. Merge strategy consequences for a changed cached entity that exists in the data source.

Merge Strategy Current Added Deleted Detached Modified Post Current

PreserveChanges Y NC NC NC NC Y

N NC NC NC NC N

OverwriteChanges Y or N OW OW OW OW Y

PreserveChangesUnless OriginalObsolete

Y ---- NC NC NC Y

N OW OW OW OW Y

PreserveChangesUpdateOriginal Y or N NC NC NC NC Y

NC = No change; preserve the current version values of the cached entity

OW = Overwrite the cached entity’s current version values with data from the data source entity

Post Current = ‘Y’ means the cached entity is “current” relative to the data source after the merge.

There are important artifacts not immediately observable from this table.

53 The possible values are Added, Deleted, Detached, Modified, and Unchanged. See “Data Row State” in the

glossary.

Page 253: Dev Force 2010 Developers Guide

253 | P a g e

The entity’s EntityState may change after the merge. It will be marked Unmodified after merge with

OverwriteChanges. It will be marked Unmodified after merge with

PreserveChangesUnlessOriginalObsolete if the entity is obsolete.

Note that deleted and detached entities are resurrected in both cases.

An added cached entity must be deemed “obsolete” if it already exists in the data source54. We will not

be able to insert that entity into the data source; we’ll have to update the data source instead.

The PreserveChangesUpdateOriginal strategy enables us to force our changes into the data source

even if the entity is obsolete. An added entity merged with PreserveChangesUpdateOriginal will be

marked Modified so that DevForce knows to update the data source when saving it.

These effects are summarized in the following table:

Table 12. EntityState after merge.

Merge Strategy Current Added Deleted Detached Modified

PreserveChanges Y or N A D Dt M

OverwriteChanges Y or N U U U U

PreserveChangesUnless OriginalObsolete

Y --- D Dt M

N U U U U

PreserveChangesUpdateOriginal Y or N M D Dt M

A = Added, D = Deleted, Dt = Detached, M = Modified, U = Unchanged

The merge may change the original version of a changed cached entity to match the data source values.

PreserveChanges never touches the original version.

The original version is always changed with the OverwriteChanges strategy.

It is reset with the PreserveChangesUnlessOriginalObsolete strategy if (and only if) the entity is

obsolete..

PreserveChangesUpdateOriginal updates the original version (but not the current version!) if the

entity is obsolete. This step ensures that the cached entity appears current while preserving the pending

changes.

These effects are summarized in the following table:

54 The entity exists in the data source if the query returns an object with a matching primary key. If we think we created

Employee with Id=3 and we fetch one with Id=3, someone beat us to it and used up that Id value. Our entity is obsolete.

Page 254: Dev Force 2010 Developers Guide

254 | P a g e

Table 13. Merge strategy effect on the original version of the cashed entity.

Merge Strategy Current Added Deleted Detached Modified

PreserveChanges Y or N NC NC NC NC

OverwriteChanges Y or N OW OW OW OW

PreserveChangesUnless OriginalObsolete

Y ---- NC NC NC

N OW OW OW OW

PreserveChangesUpdateOriginal Y or N OW OW OW OW

Merging when the cached entity is not in the data source

We begin by considering cached entities that are unchanged. If the query applied to the cache returns

an unchanged entity, ‘X’, and the query applied to the data source did not return its mate, we can safely

assume that ‘X’ was deleted after we fetched it. We can remove ‘X’ from the cache.

We turn next to changed cached entities where we must distinguish between a query that tests only for

the primary key and one that tests for something other than the primary key.

If the query tests for anything other than the primary key, we can draw no conclusions from the fact

that a cached entity was not found in the database. For what does it mean if we have an employee

named “Sue” in cache and we don’t find her in the data source? Perhaps someone deleted her from the

data source. Maybe someone merely renamed her. Maybe we renamed her. The combinations are too

many to ponder.

On the other hand, if we query for Employee with Id = 3 and we don’t find that employee in the data

source, we can be confident of a simple interpretation55. A business object must have unique identity so

if it isn’t there, either it was never there or it has been deleted. What happens next depends upon the

EntityState of the cached entity and the merge strategy.

DevForce recovers gracefully when it attempts to save an entity marked for deletion and it can‟t find the

data source entity to delete so the merge can leave this cached entity alone. It can also skip over the

detached entities.

PreserveChanges forbids merge effects on changed entities. The entity stays put in the cache.

OverwriteChanges takes the data source as gospel. If the cached entity‟s EntityState is Modified,

there should be an existing data source entity. There is not, so DevForce assumes the data source entity has

been deleted and the cache should catch up with this reality. It removes56

the entity from the cache.

On the other hand, if the cached entity is new (Added), we don‟t expect it to be in the data source. The

entity remains “as is” in the cache, a candidate for insertion into the data source.

PreserveChangesUnlessOriginalObsolete behaves just like OverwriteChanges.

55 DevForce confirms that the primary key has not changed. While it is good practice to use immutable keys, it is not always

so. If the primary key has been changed, DevForce leaves the cached entity alone.

56 Removal from the cache is just that. The entity disappears from cache and will not factor in a save. It does not mean “delete”

which requires DevForce to try to delete the entity from the data source. It is an action neutral to the data source..

Page 255: Dev Force 2010 Developers Guide

255 | P a g e

PreserveChangesUpdateOriginal strives to position the entity for a successful save. It must intervene

to enable data source insertion of a modified entity by changing its EntityState to Added57

.

In sum:

Table 14. Merge strategy consequences for a changed cached entity that does not exist in the data source.

Merge Strategy Added Modified

PreserveChanges A M

OverwriteChanges A R

PreserveChangesUnlessOriginalObsolete A R

PreserveChangesUpdateOriginal A A

A = Added, M = Modified, R = Removed

DataSourceOnly Subtleties We may get a nasty surprise if we use a DataSourceOnly or DataSourceThenCache query with other

than the OverwriteChanges merge strategy. Consider the following queries using the PreserveChanges

merge strategy.

Suppose we hold the “Nancy” employee in cache. We change her name to “Sue” and then search the

database for all Employees with first names beginning with ‘S’. We will not get “Sue” because she is still

“Nancy” in the database.

Suppose we search again but this time we search for first names beginning with ‘N’. This time we get

“Sue”. That will confuse the end user but it is technically correct because the “Sue” in cache is still

“Nancy” in the database58.

The EntityManager.AttachEntity Method

Those of you who write tests and don't want those tests to touch the database will appreciate this

method. Here is its signature:

C#

AttachEntity(object entity)

As you know, you sometimes need to write tests which rely upon interaction with the EntityManager.

You want to populate a disconnected EntityManager with a small collection of hand-rolled stub entities.

While such tests are integration tests because they rely on a dependency, we still want to make them

easy to write and we want them to be fast. That means we don't want a trip to a database when we run

them; we shouldn't need to have a database to run them.

57 An update would fail because there is no data source entity to update.

58 DataSourceThenCache will produce the same anomaly for the same reason: the database query picks up the object in

the database as “Nancy” but preserves the modification in cache which shows her as “Sue”.

Page 256: Dev Force 2010 Developers Guide

256 | P a g e

I usually start by creating a test-oriented, disconnected EntityManager ... which can be as simple as the

following:

C#

var testManager = new EntityManager(false /* disconnected */ );

The easiest way to get a stub entity is to "new" it up, set some of its properties, give it an EntityKey, and

dump it in our testManager. When we're done it should appear there as an unchanged entity ... as if you

had read it from the datastore.

The catch lies in the answer to this question: "How do I add the entity to the manager?"

In the absence of AttachEntity() method, you would have to use EntityManager.AddEntity(). But after

AddEntity, the EntityState of the entity is always "Added". You want a state of "Unchanged" so you have

to remember to call AcceptChanges (which changes the state to "Unchanged").

That's not too hard. Unfortunately, it gets messy if the key of the entity is auto-generated (e.g., mapped

to a table whose id field is auto-increment) because DevForce automatically replaces your key with a

temporary one as part of its auto-id-generation behavior.

We could explain how to work around this, but what was really needed was a simple way to simulate

the result of retrieving an entity. That's why we created the AttachEntity() method.

Here's the XML documentation for AttachEntity:

Adds a detached entity to this EntityManager in an Unmodified state. Throws an

exception if an entity with the same key already exists in the manager of if the specified

entity is not in a detached state.

Let us elaborate here and compare it to some similar methods by calling out some facts about

the following code fragment:

C#

theEntityManager.AttachEntity(object theEntity)

theEntity‟s EntityKey (“the key”) must be preset prior to the attach operation (which will not touch the

key).

An exception is thrown if an entity with that key is already in the cache.

After attach, theEntity is in an “Unchanged” EntityState (“the state”).

theEntity is presumed to exist in the persistent store; a subsequent change and save will translate to an

update statement.

Page 257: Dev Force 2010 Developers Guide

257 | P a g e

After a successful attach, a reference to theEntity is a reference to the entity with that key in the

manager‟s EntityCache. Contrast this with the effect of anEntityManager.Imports(new [] {anEntity})” as

discussed below.

theEntity must be in the “Detached” state prior to the operation.

An exception is thrown if theEntity is other than in “Detached” state prior to the operation.

After attach, related entities are implicitly associated with theEntity automatically; for example, if

anOrder with Id==22 is attached and there are OrderDetails with parent OrderId==22, then after the

attach, anOrder.OrderDetails returns these details and any one of them will return „anOrder‟ in response

to anOrderDetail.Order.

The sequence of attachments is not important; OrderDetails may be added prior to the parent Order.

Attach has no effect on theEntityManager‟s QueryCache.

AddEntity behaves the same way as AttachEntity except as follows:

After add, theEntity is in an “Added” state

theEntity is presumed to be new and to be absent from in the persistent store; a save will translate to an

insert statement.

If the key for this type is auto-generated (e.g., backed by an auto-increment field in the database), the

existing key will be set to a generated temporary key, replacing the prior key value.

The following is true regarding detaching anEntity:

After detach, anEntity enters the “Detached” state no matter what its prior state.

Detaching an Order does not detach its child OrderDetails; they remain “orphaned” in the cache.

The sequence of detachments is not important; an Order may be detached prior to detaching its child

OrderDetails.

Detach has no effect on theEntityManager‟s QueryCache.

EntityManager.Imports is another way of populating an EntityManager with a collection of entities that

may have come from anywhere (including hand-rolled). Here's how you might "import" a single stub

entity:

C# theEntityManager.Imports(new [] {theEntity}) ;

Imports differs from AttachEntity in that:

It requires a MergeStrategy to tell it what to do if an entity with the same key as "theEntity" already exists

in the cache.

It merges "theEntity" into the cache based on the MergeStrategy

It makes a clone of "theEntity" and adds that clone to the EntityCache ... unless "theEntity" happens to

already be in the cache in which case it is ignored ... which means that

Using our example and assuming that "theEntity" was not already in the manager, the entity instance in

the cache is not the same as the entity instance you imported, although their keys are equal; the following

is true:

C#

theEntity != theManager.FindEntity(theEntity.EntityAspect.EntityKey)

Page 258: Dev Force 2010 Developers Guide

258 | P a g e

A "clone" is a copy of an entity, equivalent to calling the following:

C#

((ICloneable)theEntity).Clone();

This is a copy of the entity, not of its related entities.

Filtering Queries

DevForce provides an extension method, Filter(), that can be used to superimpose one or more

independently defined filter conditions upon an existing query. Filter() differs from Where() in that it can

apply a condition defined independent of the targetted query. Filter()’s primary motivating use case is

the need to apply server-side filters to submitted queries in a handler for the Server.Fetching event;

though it is perfectly possible to use it in other contexts.

For example, suppose your application’s database includes data for customers worldwide, but that a

given Sales Manager only works with data for customers from his region. Instead of baking the region

condition into every query for Customers throughout your application, you could implement a

ServerFetching handler that imposes the condition upon any query for customers made while that Sales

Manager is logged in.

The usefulness of Filter() becomes even more apparent when you need to apply filters in a global way

for more than one type.

There are four overloads of Filter(), two of which are generic, and two of which are not. Each pair

includes one overload that takes a Func<T> and another that takes an EntityQueryFilterCollection (each

of whose members is a Func<T>). The generic versions normally get used client-side, because they

normally operate upon an EntityQuery<T>, whereupon.NET uses type inference to get T and route the

call through the generic signature. The non-generic versions are necessary because, server-side,

DevForce has access only to an EntityQuery, not an EntityQuery<T>; that being a consequence of the

.NET constraint that generic types can’t be passed in event arguments.

Let’s look at some examples:

C#

var query = _em1.Territories.Where(t => t.Id > 100);

var newQuery = query.Filter((IQueryable<Territory> q) =>

q.Where(t => t.Description.StartsWith("M")));

In this example we have used the overload of Filter which is non-generic, and which takes as its

argument a Func delegate. Said delegate takes an IQueryable<T> -- essentially a list of items of type T –

and returns an IQueryable<T>. The IQueryable<T> that goes in is the one defined by the variable query,

defined as

Page 259: Dev Force 2010 Developers Guide

259 | P a g e

C# _em1.Territories.Where(t => t.Id > 100)

The one that comes out is the one that went in minus those Territories whose Description property

value begins with the letter “M”.

In the first example, above, our filter applies to the query’s root type, Territory. We aren’t limited to

that: we can also apply filters to other types used in the query. Consider the following:

C#

var q1 = _em1.Customers.SelectMany(c => c.OrderSummaries

.Where(o => o.ShipCity.StartsWith("N")) );

var q1a = q1.Filter((IQueryable<OrderSummary> q) => q.Where(o => o.Freight > maxFreight));

The root type for this query is Customer, but the query projects OrderSummaries as its output, and it is

against OrderSummaries that we apply our filter. Again we use the non-generic form of Filter; and again,

the overload that takes a Func<T> argument. This time the filter imposes a condition upon the values of

the OrderSummary.Freight property. Without the filter we would have retrieved all OrderSummaries

having a ShipCity whose name begins with “N”; with the filter, not only must the name begin with “N”,

but the Freight property value must exceed the value maxFreight.

Let’s look at another example of filtering one some type other than the query’s root type:

C#

var q1 = _em1.Customers.Where(c => c.OrderSummaries.Any(o => o.ShipCity.StartsWith("N")));

var q1a = q1.Filter((IQueryable<OrderSummary> q) => q.Where(o => o.Freight > maxFreight));

In the absence of the filter, the above query would retrieve Customer objects: specifically, Customers

having at least one Order whose ShipCity begins with the letter “N”. The filter potentially reduces the set

of Customers retrieved by imposing an additional condition on their related OrderSummaries (again, on

the value of their Freight property).

Now let’s look at a use of Filter() involving conditions on more than a single type.

C#

var eqFilters = new EntityQueryFilterCollection();

eqFilters.AddFilter((IQueryable<Customer> q) => q.Where(c => c.Country.StartsWith("U")));

eqFilters.AddFilter((IQueryable<OrderSummary> q) =>

q.Where(o => o.OrderDate < new DateTime(2009, 1, 1)));

var q0 = _em1.Customers.Where(c => c.OrderSummaries.Any(o => o.ShipCity.StartsWith("N")));

var q1 = q0.Filter(eqFilters);

Page 260: Dev Force 2010 Developers Guide

260 | P a g e

In the above snippet, we instantiate a new EntityQueryFilterCollection, to which we then add two

individual filters, each of which is a Func<T>. The first filter added imposes a condition on the Customer

type; the second imposes a condition on the OrderSummary type. Note that we could now apply these

filters to any query whatsoever. If the targetted query made use of the Customer type, the condition on

Customers would apply; if it made use of the OrderSummary type, the condition on OrderSummaries

would apply. If it made use of both, as does our example q0, both conditions would apply.

A filter is also applied directly to any clause of a query that returns its targetted type. Thus, the effect of

the two filters defined above, applied against query q0, is to produce a query that would look like the

following if written conventionally:

C#

var q0 = _em1.Customers

.Where(c => c.Country.StartsWith("U"))

.Where(c => c.OrderSummaries

.Where(o => o.OrderDate < new DateTime(2009, 1, 1))

.Any(o => o.ShipCity.StartsWith("N")));

Query Inversion in More Detail

The discussion here expands upon that in the section “InversionMode” earlier in this chapter. It is

provided as a supplement for a deeper understanding of the topic.

Interaction of the FetchStrategy and the InversionMode

Consider the query shown below. For this query, we have custom-baked a QueryStrategy so we can

experiment with various FetchStrategies and InversionModes.

The collection against which the query is directed is _Em1.Customers; but then it uses the SelectMany()

method to project Order objects into the result set. Since its return type is different from the type

contained in the collection first referenced, the query is non-invertible.

Page 261: Dev Force 2010 Developers Guide

261 | P a g e

C# var query = _Em1.Customers

.Where(c => c.CustomerID == "CONSH")

.SelectMany(c => c.Orders);

QueryStrategy aQueryStrategy =

new QueryStrategy(FetchStrategy.DataSourceThenCache,

MergeStrategy.PreserveChanges, InversionMode.On);

query.QueryStrategy = aQueryStrategy;

foreach (Order anOrder in query) {

System.Diagnostics.Debug.WriteLine(anOrder.OrderDate.ToString());

}

Assert.IsTrue(query.ToList().Count > 0, "should return orders");

VB

In our initial run, we have the InversionMode set to On. Because DevForce is unable to invert the query,

a QueryInversionServerException is thrown, with the following message:

This query is not automatically invertible and cannot be

executed unless either its QueryInversionMode is set to 'Manual'

or its FetchStrategy is set to Optimized, DataSourceOnly or

CacheOnly.

If we change the InversionMode to Try and rerun the query, it runs without an exception, but the Assert

test fails, because no Orders were included in the result set. Why? Because changing the

InversionMode from On to Try didn’t alter the fact that the query couldn’t be inverted; it just told

DevForce not to worry about that fact. The result set returned with a FetchStrategy of

DataSourceThenCache is only that obtained in a final query against the cache, after entities retrieved

from the data source have been placed there. Since the query was not invertible, no Customer objects

were retrieved into the cache, and that final query returns an empty result.

Suppose now we set the FetchStrategy to DataSourceAndCache. Now references to the Order objects

retrieved from the data source are included in the result set. A second application of the query, this time

against the cache, may or may not pick up additional Orders59. But in any event, the final result set will

contain references to the in-cache Orders that are linked to the specified Customer. This will be true

even if, at the end of the process, there are still no Customer objects in the cache!

When a query cannot be inverted, a FetchStrategy other than DataSourceThenCache should be used.

Table 15 shows the combinations of FetchStrategy and InversionMode that lead to exceptions. Note that

these exceptions are designed to prevent you from receiving query results that, although they may look

perfectly valid, are not!

59 It will pick up additional Orders if there are Orders in the cache that are (a) linked to Customer “CONSH”, and (b) either do

not exist in the data source, or are not linked to Customer “CONSH” in the data source

Page 262: Dev Force 2010 Developers Guide

262 | P a g e

Table 15. FetchStrategy x InversionMode - Exception Behavior

FetchStrategy InversionMode QueryInversionServerException

CacheOnly NA Never

DataSourceOnly On If query requires inversion and cannot be inverted

DataSourceOnly Try Never

DataSourceOnly Off Never

DataSourceOnly Manual Never

DataSourceThenCache On If query requires inversion and cannot be inverted

DataSourceThenCache Try If query requires inversion and cannot be inverted

DataSourceThenCache Off If query requires inversion

DataSourceThenCache Manual Never

Optimized On If query requires inversion and cannot be inverted

Optimized Try Never

Optimized Off Never

Optimized Manual Never

DataSourceAndCache On If query requires inversion and cannot be inverted

DataSourceAndCache Try Never

DataSourceAndCache Off Never

DataSourceAndCache Manual Never

Only queries that either have been inverted or do not require inversion are saved in the query cache.

Turning a Non-Invertible Query on Its Head

Note that the previous query (for Orders placed by Customer “CONSH”) can be rewritten as follows:

C# var query = _Em1.Orders

.Where(o => o.Customer.CustomerID == "CONSH");

VB

This form of the query, unlike the other one, is invertible.

A Special Case: Using the Skip() Method on an EntityQuery

The query below uses the DataSourceOnly QueryStrategy in combination with a call to Skip().

Page 263: Dev Force 2010 Developers Guide

263 | P a g e

C#

EntityQuery<Customer> customersQuery = _Em1.Customers

.Where(c => c.ContactTitle == "Sales Representative")

.OrderBy(c => c.CompanyName);

customersQuery.QueryStrategy = QueryStratey.DataSourceOnly; // <--note!

ICollection<Customer> customers = customersQuery.Skip(5).Take(5).ToList();

VB

You can easily get results that are not what you would expect if you do not specify the QueryStrategy

when using Skip. Suppose we omitted the statement in the above example that specifies the

QueryStrategy:

C#

EntityQuery<Customer> customersQuery = _Em1.Customers

.Where(c => c.ContactTitle == "Sales Representative")

.OrderBy(c => c.CompanyName);

ICollection<Customer> customers = customersQuery.Skip(5).Take(5).ToList();

VB

In the above case, DevForce would use the EntityManager’s default QueryStrategy, which (unless you

had changed it) would be QueryStrategy.Normal. Recall that QueryStrategy.Normal uses a FetchStrategy

of DataSourceThenCache, and that the latter returns a list of references obtained in a final, cache-only

query.

So here’s the flow of events for the above query. (Assume an empty cache as a starting point.)

1. Query is submited to the EntityManager.

2. EntityManager checks the query cache to see if query has been submitted before. It finds that it has not.

3. EntityManager submits query against the data source, which returns five Customers, which are placed in

the cache.

4. EntityManager submits the query again, this time against the cache (so that it will incorporate any

Customers who have been added locally but have not yet been saved to the data source).

The second query, against the cache, skips the five Customers it finds there, and upon attempting to

take the next five, discovers that there are no more. It therefore returns 0 Customers.

Although this isn’t, technically, a case of a failed query inversion, the result and the reason for it are

clearly similar to that. The only real advice here is that, if you’re using Skip(), you should either use a

FetchStrategy of DataSourceOnly, or make good and certain that you understand FetchStrategies in

detail.

DataSourceThenCache Versus DataSourceAndCache

The distinction between the DataSourceThenCache and DataSourceAndCache strategies is subtle but

important in the case of queries that must process non-targeted types and are therefore subject to

query inversion. Suppose you were to submit the following query:

Page 264: Dev Force 2010 Developers Guide

264 | P a g e

C# var query = em1.Customers

.Where(c => c.Orders

.Any(o => o.OrderDate.HasValue == true &&

o.OrderDate.Value.Year == 1997));

VB

This query targets Customers but must process Orders to find the correct set of Customers. DevForce

would have no difficulty inverting this query, but suppose you submitted it with an InversionMode of Off

and a FetchStrategy of DataSourceThenCache. The InversionMode setting would mean that only

Customer objects were retrieved into the cache: no Order objects. “Great!” you say. “That’s all I wanted:

Customers.” But even though you have the desired Customers in your cache, you don’t yet have

references to them.

How does DevForce get these references? Because of the FetchStrategy you specified, DevForce now

resubmits your query, this time against the cache; and the set of references to Customer objects that it

will return will be entirely determined by the Customers that meet the query criteria when the query is

resubmitted against the cache. But wait! There is no guarantee that the cache contains the same Order

objects that were found in the data source; it will, in fact, contain no Order objects at all unless some

other, unrelated operation that was previously executed caused some to be retrieved. Therefore the

set of Customers found by the query when submitted against the cache may be very different from the

set found when it was submitted against the data source. Indeed, the set may be empty. You may get

references to no Customers or some Customers, but there is no guarantee, and indeed little likelihood,

that you’ll get references to all of the Customers retrieved by your query from the data source.

If, on the other hand, you submitted your query with a FetchStrategy of DataSourceAndCache, you’ll get

want you wanted: all Customers in the data source who meet your conditions, as well as all Customers

that exist only in your local cache that meet those conditions. With that FetchStrategy, DevForce

performs a union of the references obtained by the two query submissions.

The DataSourceAndCache FetchStrategy does have some drawbacks which we’ll discuss momentarily.

Generally speaking, it is the appropriate FetchStrategy only in the following circumstance:

1. Your query will use related objects;

2. You want to include in the result set references to entities that exist in the cache but which

have not yet been persisted to the database;

3. DevForce can’t invert the query; and

4. You can’t write an equivalent query that is invertible.

The reason that DataSourceThenCache is the preferred FetchStrategy for other circumstances is that,

under certain circumstances, DataSourceAndCache can produce confusing results. Suppose you have

some Customer objects in the cache, including Customer XYZ, and you submit a DataSourceAndCache

query for Customers with Orders in the current year. Customers meeting this condition are fetched from

the data source into the cache, and merged there with Customers already residing in the cache with a

Page 265: Dev Force 2010 Developers Guide

265 | P a g e

MergeStrategy of PreserveChanges. Meanwhile DevForce hangs on to a list of references to the objects

just fetched.

Now it so happens that Customer XYZ, who was in the cache already, had (during the current application

session) just cancelled their one and only order for the current year. The Order was marked for deletion,

but this change had not been committed to the database when the query was submitted. So, based on

the state of data in the data source, Customer XYZ met the query conditions and was retrieved, and a

reference to their object in the cache was included in the set returned by the query against the

datasource.

DevForce continued on, resubmitting the query against the cache. This time Customer XYZ did not make

the cut because, according to the data in the cache, they did not have a current year Order. No

reference to their in-cache object was included in the list of pointers resulting from the query against

the cache.

But DataSourceAndCache, DevForce then performed a UNION of the references obtained in the query

against the data source and those obtained in the query against the cache. A reference to cached

Customer XYZ therefore ended up in the result set returned by the query. Your app happily filled a

datagrid with the returned Customers, and there sat Customer XYZ, even though they (quite visibly) did

not have an order in the current year! Can a phone call from your end user be far away?

The DataSourceThenCache FetchStrategy, by contrast, would have retrieved, from the data source and

into the cache, whatever data met the query conditions. It would then have submitted the query against

the cache, and only the Customers meeting the specified condition in that final query would have been

included in the returned result set. Customer XYZ, having been found to have no current year Order,

would have been excluded, properly.

Transactional Queries

DevForce query requests are atomic: the developer can issue only one (synchronous) query request at a

time. But when the request resolves into multiple SQL queries, they can all be performed together

within the same transaction.

Individual query requests resolve into several SQL queries when the query has includes that fetch

related objects or when the query includes one or more sub-queries and “query inversion” is turned on.

When the root query is performed transactionally, both the main select and the selection of related

entities occur within transactional boundaries.

DevForce developers can set the transaction isolation level on individual commands Developers can set the transaction isolation level for individual queries and saves.

Implementation

Page 266: Dev Force 2010 Developers Guide

266 | P a g e

There is a TransactionSettings class and a TransactionSettings property on both the SaveOptions

and QueryStrategy classes.

The TransactionSettings class provides the ability to dynamically set:

whether or not to use the Microsoft Distributed Transaction Coordinator

the Transaction Isolation level of a Save or Query. This provides in effect

the Transaction timeout to be applied to a Save or Query Note: For the current version Transaction isolation levels and timeouts can only be applied if the DTC is

turned on.

Note: The Default Transaction Isolation Level for Saves is “Serialized”; for queries it is “ReadCommitted”.

Note: Non-locking queries can be implemented by setting the TransactionSettings.IsolationLevel to

“ReadUncommitted”.

DevForce and Data Sources – Deep Dive

There are potentially many data sources at play in a DevForce application. Data sources can be

databases or, using DevForce POCO support, any type of data source, including web services.

Any design-time access of a database is initiated by Visual Studio’s Entity Data Model Designer during

your design session using that tool. That designer adds a connectionString element to the app.config

file, placing it in the same project as the EDMX.. This app.config contains connection information to the

database used by the EDM designer for its design work.

The DevForce OM Extension to the EDM Designer adds a DataSourceKey attribute to the EDMX. If you

open the EDMX using an XML editor, you’ll see that the DevForce-added attributes are prefixed with the

namespace qualifier of “ib10”. By default, the DataSourceKey is given the same name as the EF Entity

Container Name, and thus points to a connection string in the app.config of the Model project.

XML

Page 267: Dev Force 2010 Developers Guide

267 | P a g e

<connectionStrings>

<add name="NorthwindIBEntities" connectionString="… "

providerName="System.Data.EntityClient" />

</connectionStrings>

The EDM Designer updated an app.config for design time support. At run time, DevForce will use the

DataSourceKey value to find the connection information to the run time database. To do so, the

connection information must be available in the app.config of the executable project (or web.config),

not the Model project. DevForce will search for an edmKey or connectionString with the same name as

the DataSourceKey + DataSourceExtension. We haven’t previously mentioned EdmKeys, but they

encapsulate connection information, and are located within the IdeaBlade Configuration section. An

EdmKey contains connection information, as a connectionString will, but also additional DevForce

attributes, such as logTraceString, which writes the EF-generated SQL to the debug log. You must

ensure that either an edmKey or connectionString is available at run time in the appropriate config file.

DevForce also allows you to omit connection information from the run time configuration altogether

and supply the connection string dynamically, using the IDataSourceKeyResolver interface. More

information on implementing this interface is available later in this document.

DataSourceKeys, DataSourceKeyResolvers, and DataSourceExtensions A DataSourceKey is a symbolic representation of a data source used by the DevForce EntityManager and

associated with the Entity objects it retrieves, updates, and creates. Every Entity has a

“DataSourceKeyName” attribute that identifies its symbolic Datasource60. This key name is hard-coded

into the business class at the time the latter is generated by DevForce.

Recall that a DomainModel, and therefore an EntityManager can access multiple data sources. A given

EntityManager might, for example, access a SQL Server database, an Oracle database, and a web

service, mapping business classes from each and joining all into a single transactional unit. Each of those

three datasources gets a distinct DataSourceKey, and entities generated from each of them get assigned

the name of that DataSourceKey.

But what if you need multiple versions of those three data sources? For example, you might have

Development, Test, Stage, and Production versions of the same three-datasource set. The data sources

in all four versions would have the same schemas, but different content. For example, data in the

development and test data sources might be “scrubbed” so as to eliminate security issues during

relatively unprotected use; data in the development data sources might be lightweight compared to that

60 You can find this on an entity instance as its EntityAspect.EntityMetadata.DataSourceKeyName property

Page 268: Dev Force 2010 Developers Guide

268 | P a g e

in the Test data sources; and so forth. All four versions of a given database (schema) in a set of data

sources would be identified with the same DataSourceKey and all would map to the same set of

business classes, so that an application consuming their data would be indifferent to which of the

physical instances of that schema it accessed in any given launch.

DevForce uses a string called a DatasourceExtension to discriminate between alternative instances of a

given data schema. You supply these extensions in the edmKeys (or connectionStrings) that you

configure in the app.config file, by adding them to the name attribute of the edmKey, separating them

from the DataSourceKey Name by an underscore character (“_”).

At runtime, to obtain the data required for business objects of a designated type (e.g, Employees),

DevForce connects to an actual data source by consulting a DataSourceKeyResolver. The

DataSourceKeyResolver combines the DataSourceKeyName associated with the desired Entity type

with a DataSourceExtension (supplied by the requesting EntityManager) and returns a DataSourceKey

object. That DataSourceKey object contains all the information required to connect to an actual,

deployed data source.

EntityManagers and DataSourceExtensions

Every EntityManager gets associated at instantiation with a “DataSourceExtension”. You can see this clearly in

the following code statement which uses an overload of the EntityManager constructor that specifies the extension

explicitly (as “Development”):

C#

DomainModelEntityManager mgr = new DomainModelEntityManager(true, "Development");

The extension determines which version – e.g., Development, Test, Stage, or Production – of the data source(s)

actually gets accessed by the EntityManager. Expressed another way: the “Extension” identifies a collection of

one or more data sources, each the repository of a set of tables or web services that map to business object classes,

which will be accessed by a given EntityManager. 61

In the illustration below, all four of the DS#1 data sources would have the same DataSourceKeyName.

The same could be said for the DS#2 and DS#3 data sources. On the other hand, the set of data sources

accessed by a single EntityManager would comprise a DS#1, a DS#2, and a DS#3. But which copy of

DS#1, a DS#2, and DS#3 should be used? That would be determined by the DataSourceExtension with

which the EntityManager was associated at instantiation.

61 The default extension, incidentally, is no extension at all.

Page 269: Dev Force 2010 Developers Guide

269 | P a g e

Now let’s look at DataSourceKey names and extensions as they appear in edmKeys in an App.config file.

Listing 3 is an excerpt from an app.config file containing multiple DataSourceKeys with different key

names and extensions. For clarity, we’ve made sure the name attribute is the first attribute listed for

the the <edmKey> element.

Note that each DataSourceKey, in addition to containing a connection string, also includes probe

assembly names for assemblies that hold auxiliary classes for id generation, authentication, event

handling, and the like.62

Listing 3. Extract of app.config file with multiple DataSourceKeys

XML

<edmKeys>

<!-- Production databases -->

<edmKey name="NorthwindIB_Release"

connection="metadata=res://ServerModelNorthwindIB/ServerModelNorthwindIB.csdl|res://Serve

rModelNorthwindIB/ServerModelNorthwindIB.ssdl|res://ServerModelNorthwindIB/ServerModelNor

thwindIB.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data

Source=ProductionDBMS_A;Initial Catalog=NorthwindIB;Integrated

Security=True;MultipleActiveResultSets=True&quot;"

logTraceString="false" tag="">

</edmKey>

<edmKey name="Aw2000_Release"

connection="metadata=res://ServerModelAw2000/ServerModelAw2000.csdl|res://ServerModelAw20

00/ServerModelAw2000.ssdl|res://ServerModelAw2000/ServerModelAw2000.msl;provider=System.D

ata.SqlClient;provider connection string=&quot;Data Source=ProductionDBMS_B;Initial

Catalog=AdventureWorks2000;Integrated Security=True;MultipleActiveResultSets=True&quot;"

logTraceString="false" tag="">

</edmKey>

<!-- Development databases -->

<edmKey name="NorthwindIB_Development"

connection="metadata=res://ServerModelNorthwindIB/ServerModelNorthwindIB.csdl|res://Serve

rModelNorthwindIB/ServerModelNorthwindIB.ssdl|res://ServerModelNorthwindIB/ServerModelNor

thwindIB.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data

Source=DevelopmentDBMS_A;Initial Catalog=NorthwindIB;Integrated

Security=True;MultipleActiveResultSets=True&quot;"

logTraceString="false" tag="">

</edmKey>

<edmKey name="Aw2000_Development"

connection="metadata=res://ServerModelAw2000/ServerModelAw2000.csdl|res://ServerModelAw20

62 It may also contain a <tag>, where you can put any sort of string-value custom information you desire. At runtime you can

access the information placed there via the Tag property of a DataSourceKey object -- which you can get from the

DataSourceKeys collection of a DataSourceKeyResolver object.

Page 270: Dev Force 2010 Developers Guide

270 | P a g e

00/ServerModelAw2000.ssdl|res://ServerModelAw2000/ServerModelAw2000.msl;provider=System.D

ata.SqlClient;provider connection string=&quot;Data Source= DevelopmentDBMS_B;Initial

Catalog=AdventureWorks2000;Integrated Security=True;MultipleActiveResultSets=True&quot;"

logTraceString="false" tag="">

</edmKey>

</edmKeys>

In the above excerpt from an app.config file, edmKeys are present for two databases (NorthwindIB and

Adventureworks2000). Two versions (Development and Release) are maintained of these databases.

Instantiating an EntityManager and specifying a DataSourceKey Extension of “Release”...

mPersMgr = new DomainModelEntityManager(true, "Release");

…would cause all data accesses for entities based on the databases to go against the sources named in

the edmKeys that have the suffix “_Release” in their name attribute. For example, data for classes

mapped to tables in the NorthwindIB database would be retrieved from the copy of that database

running on the ProductionDBMS_A instance of SQL Server; data for classes mapped to the

AdventureWorks2000 database would be retrieved from the copy of that database running on the

ProductionDBMS_B instance of SQL Server. Were an EntityManager to be instantiated with the

extension “Development”, different copies of the two databases would be accessed.

Note the following points:

1. The DataSourceKey Names and DataSourceKey Extensions are case insensitive.

2. In the name attribute of the edmKey element, the Datasource Extensions are always preceded by an underscore “_” character.

If you wished to establish one or the other set of databases as the default – say, the Development

versions – then you could include in the <edmKeys> section of the app.config an additional pair of

edmKeys with no extensions specified in their names, as shown below. The information in these keys

would be used by any EntityManager instantiated with no DataSourceExtension specified. (This time, for

brevity, we’ve snipped out the detail for the connection attribute value.)

Listing 4. Extract of app.config file with multiple DataSourceKeys

XML

<!-- Default databases -->

<edmKey name="NorthwindIB" connection="..."

logTraceString="false" tag="">

</edmKey>

<edmKey name="Aw2000" connection="..."

logTraceString="false" tag="">

</edmKey>

Page 271: Dev Force 2010 Developers Guide

271 | P a g e

Tenant Extensions Extensions are also a good way to segment data sources by client in a “multi-tenant application”. Multi-

tenant applications are typical of Application Service Provider (ASP) scenarios in which each customer’s

data is managed in isolated data sources.

When the user logs in, the application identifies the user’s parent customer and knows which set of

Datasources is appropriate for that user. The application can then instantiate an EntityManager that

draws upon just those data sources.

The “DataSourceExtension” is the ideal representation for a customer-specific data source set as in this depiction of

a three-tenant scenario with customers “A”, “B”, and “C”:

Multi-Part Extensions DataSourceExtensions may have multiple parts, permitted an even more sophisticated scheme for

selected a data source instance at runtime. Consider the following edmKeys in an app.config file

(connection value and probe assembly section removed for brevity):

XML

<edmKeys>

<!-- Production databases -->

<edmKey name="Acmetest2_SQLSRVR_OLE1" connection="... "

logTraceString="false" tag="">

...

</edmKey>

<edmKey name="Acmetest2_SQLSRVR_OLE2" connection="..."

logTraceString="false" tag="">

...

</edmKey>

<edmKey name="Acmetest2_SQLSRVR" connection="... "

logTraceString="false" tag="">

...

</edmKey>

<edmKey name="Acmetest2" connection="..."

logTraceString="false" tag="">

...

</edmKey>

</edmKeys>

Page 272: Dev Force 2010 Developers Guide

272 | P a g e

Note that the first two edmKey names contain two underscores. These delimit multi-part

DataSourceExtensions. Were you to instantiate an EntityManager as follows:

C#

mPersMgr = new DomainModelEntityManager(true, "SQLSRVR_OLE1");

…you would get the database identified in the first edmKey in the above excerpt. On the other hand, if

you wrote this statement:

C#

mPersMgr = new DomainModelEntityManager(true, "SQLSRVR_FOO");

…then the DataSourceKeyResolver, being unable to locate an edmKey with both parts of the extension

matching, would resolve the database to the one identified with the third edmKey, named

“AcmeTEst2_SQLSRVR”. It would do so because it finds a match on the first part of the extension.

If the DataSourceKeyResolver can find no edmKey with an extension that matches at least on the first

part of the extension submitted, it will throw an exception. Thus, the following statement, containing a

misspelled first part of the extension, will result in an exception. It will find no matching set of

extensions; no matching first extension; and will not default to the extensionless key “AcmeTest2”:

C#

mPersMgr = new DomainModelEntityManager(true, "SQLSVRR_OLE1");

Extensions and EntityServers Let’s stick with the multi-tenant, ASP scenario for awhile.

When the application client determines the customer, it creates an EntityManager dedicated to the

data sources applicable to that customer by including the customer’s “DatasourceExtension” name in

the constructor.

C#

msManager = new DomainModelEntityManager(true, "A"); // Connect to customer "A"

Now the client application tries to login or fetch entities with this EntityManager. The EntityManager

contacts the EntityService. The EntityService checks among its EntityServers for one that is

associated with extension “A”. It doesn’t find one so it creates a new EntityServer instance for

extension “A” and adds it to its collection. This EntityServer now serves every EntityManager

presenting the “A” extension.

Page 273: Dev Force 2010 Developers Guide

273 | P a g e

When the EntityService encounters EntityManagers with unknown extensions – “B” and “C” for example –, it

creates more EntityServers. The three-tenant scenario could look like this:

Dynamic DataSourceKeys and the DataSourceKeyResolver Every entity has a “DataSourceKeyName” which identifies its symbolic data source. There should be at

least one real data source somewhere that holds the data source object to which the entity is mapped.

The “DataSourceKeyName” helps DevForce find it.

The DataSourceKeyName property of the entity reveals this name; for example:

anEmployee.EntityAspect.EntityMetadata.DataSourceKeyName.

DevForce connects an actual data source at runtime by asking a DataSourceKeyResolver for the

DataSourceKey that corresponds to the key name.

To be more precise, it returns an object that implements IDataSourceKey. There are two implementations

of this interface at the moment, the EdmKey, and the ClientEdmKey. EdmKey provides access and

management information for relational databases. ClientEdmKey has no dependency on the Entity

Framework or its data sources.

A DataSourceKeyResolver has a single method, GetKey(KeyName, KeyExtension). The KeyName is

the symbolic data source name that we see inscribed in the entity’s DataSourceKeyName property. The

KeyExtension, as we have seen, is an optional string for differentiating among multiple keys each

referring to a distinct runtime data source.

DevForce uses its own DefaultDataSourceKeyResolver unless we provide an alternative. The default

version looks for either an edmKey or connectionString in the application configuration. The

App.config is a fixed file that must reside in a known place. That means the key information must be

comparatively static as well.

True, the configuration file does not have to be compiled into the application63. DevForce will prefer a

loose version of the file in the executable’s directory. We make our change, drop it into the executable’s

63 DevForce supports either embedded or loose configuration files.

Page 274: Dev Force 2010 Developers Guide

274 | P a g e

directory, and DevForce will prefer that version over any other. No re-compilation or major re-

deployment required.

We may need more flexibility or security than the configuration file affords in situations such as the

following:

The connection facts change periodically and we can‟t count on redeploying the updated configuration

file.

The connection facts are different for different users of the application.

The connection facts must not reside in a text file; they must be delivered to the application at runtime

after authenticating the user.

Custom DataSourceKeyResolver Fortunately, it is easy to write a custom DataSourceKeyResolver that does exactly what you want it to

do.

Pick a project to hold your key resolver, e.g. DomainModel

Add the following references to that project: IdeaBlade.EntityModel IdeaBlade.Core

Write a class that implements IDataSourceKeyResolver.

Decorate the class with the SerializableAttribute ([Serializable] in C#, <Serializable()>_

in VB).

Implement your version of GetKey(KeyName, KeyExtension) to handle the keys you want to manage.

Return null (Nothing in VB) if you want the DefaultDataSourceKeyResolver to determine the key.

C#

using IdeaBlade.EntityModel;

using IdeaBlade.Core;

namespace AppHelper {

[Serializable]

class MyDataSourceKeyResolver : IDataSourceKeyResolver {

public IDataSourceKey GetKey(string keyName, string keyExtension, bool onServer) {

if (!onServer) {return null;}

Console.WriteLine("Shot ya with my resolver"); // Demo code.

// Build your own ClientEdmKey starting with the following

// return new MakeClientEdmKey(keyName, theConnectionString)

return null; // Didn't build key; DefaultDataSourceKeyResolver takes over

}

}

}

VB

Imports IdeaBlade.EntityModel

Page 275: Dev Force 2010 Developers Guide

275 | P a g e

Imports IdeaBlade.Core

<Serializable()> _

Public Class MyDataSourceKeyResolver : Implements IDataSourceKeyResolver

Public Function GetKey(ByVal keyName As String, _

ByVal keyExtension As String, _

ByVal onServer As Boolean ) _

As IDataSourceKey Implements IDataSourceKeyResolver.GetKey

If !onServer Then Return null

Console.WriteLine("Shot ya with my resolver") ' Demo code.

' Build your own ClientEdmKey starting with the following

' Return New MakeClientEdmKey(keyName, theConnectionString)

Return Nothing ' Didn't build key; DefaultDataSourceKeyResolver takes over

End Function

End Class

THE NET RESULT OF KEY LOOKUP MUST DELIVER A KEY ON BOTH CLIENT AND SERVER. However, in n-

tier, the client should not provide connection info in the key. Note that GetKey receives a boolean

onServer parameter that indicates whether GetKey() is operating on the server or client.

Multiple Application Environments

Many IT shops prescribe separate Development, QA, Test, Stage, and Production environments. Each

version of the application works its way through a testing gauntlet from the developer environment to

ultimate production release.

Suppose our application refers to a database data source called “default”. Its data source key is

“default”. The application will use this key at runtime to find a data source configuration in the

application configuration file (IdeaBlade.ibconfig).

The data source configuration is very simple for the development environment. The development

deployment puts all tiers on the PC. The “default” development configuration’s connection string points

to a database on the PC.

The QA environment, on the other hand, has a 3 tier deployment with separate machines for client,

business object server, and database. This requires many changes to the “default” configuration

including a different connection string that points to the QA database. We really need a separate

“default” configuration for QA.

In fact, we need five “default” configurations in the application configuration file.

The symbolic data source, “default”, doesn’t change as we cross environments. The business objects

associated with the “default” data source should be indifferent to configuration differences. The

executing environment, on the other hand, has to know which of the “default” configuration to use.

DevForce provides data source key extensions to help distinguish the five “default” data source

configurations. By convention, the data source configuration name is the data source key name followed

optionally by an underscore “_” and data source key extension.

Page 276: Dev Force 2010 Developers Guide

276 | P a g e

In our example, the configurations could be named “Default_Development”, “Default_QA”, etc. When

the application launches, it determines its runtime environment and then tells the EntityManager to

connect to its data source(s) using the extension to find the appropriate data source configuration

information64. If we execute in development, we initialize the EM with “Development” and it adds the

“_Development” suffix to “default”.

If the EntityManager (and, later, the EntityServer) cannot find a data source configuration named

“Default_Development”, it will look for one named “default” before giving up.

Multiple EntityManager Instances

Most applications only need a single EntityManager instance. A EntityManager instance can hold every

entity we need in a single cache – even entities that persist to different data sources.

Accordingly, when we write “EntityManager” we mean an instance; we say “EntityManager class”

when referring to the class rather than the instance.

We can create new instances and there are scenarios for which this is useful.

Perhaps we have a long-running query or series of queries that should run in a background thread

without blocking the UI. Maybe we want to poll for changes to a set of entities or be on the look-out for

certain conditions in the database.

Our implementation should use a different EntityManager in the background thread so as not to

conflict with the main manager in the UI thread. When the background process completes, the call-back

method can pause the UI, import data from the second manager, alert the user, and resume the UI.

Life with two EntityManagers

Each EntityManager has its own entity cache. An entity instance in one cache is not the same as an

entity instance in another cache even when the two instances have identical primary keys.

Entities with duplicate keys cannot exist within a single cache. There can be only one Employee object

with Id = 42 in a given cache. However, after reading Employee #42 into EM ‘A’ and EM ‘B’, there are

two Employee objects in the application that have Id = 42.

This is fine as long as we are aware of it. Think of the two EntityManager caches as separate clients.

When ‘A’ changes Employee #42, this has no immediate effect on ‘B’s copy of Employee #42. If ‘A’ saves

the changes, ‘B’s copy is no longer current with respect to the data source. If ‘B’ then makes changes

and tries to save, ‘B’ gets a concurrency violation.

These rules apply whether ‘A’ and ‘B’ are two end users on different PCs or two EntityManagers in the

same application.

64 Entities in the EM may map to more than one data source. The EM will suffix each data source key name with the same

extension.

Page 277: Dev Force 2010 Developers Guide

277 | P a g e

Miscellaneous observations

Different EntityManagers do not interact. It is possible – and useful – to import entities from one EM to

the other.

Logging In a Second EntityManager Based on the Credentials of Another

EntityManager

The ability to create a second EntityManager that is logged with the same credentials as the first

facilitates scenarios in which the application creates a second context for editing. Changes in this second

context are isolated from the main context and can be saved or canceled without unintended effects on

entities in the primary PM.

The EntityManager has a copy constructor for this purpose.

C# EntityManager Pm2 = new EntityManager(Pm1);

VB

The new EntityManager (Pm1) will have the same settings and credentials as its prototype (Pm1) but

without any data. Its login state will be the same as its prototype.

The second EM must connect to the database in order to save changed entities. It can only connect if it

is logged in. Without the ability to login the second EM at its creation, we would have to preserve the

users original credentials in some “safe” place in memory. This would be both inconvenient and

discomforting, as one can never be quite certain that a rogue module can be prevented from acquiring

those credentials and misusing them. It is best to forget about them as soon as possible. The new

constructor permits you to do so.

Multi-Threading in a DevForce App

Let’s begin our discussion of multi-threading with a definition of thread-safety:

For a class to be thread-safe, it first must behave correctly in a single-threaded environment. If a class is correctly

implemented, which is another way of saying that it conforms to its specification, no sequence of operations

(reads or writes of public fields and calls to public methods) on objects of that class should be able to put the

object into an invalid state, observe the object to be in an invalid state, or violate any of the class's invariants,

preconditions, or postconditions.

Furthermore, for a class to be thread-safe, it must continue to behave correctly, in the sense described above,

when accessed from multiple threads, regardless of the scheduling or interleaving of the execution of those

threads by the runtime environment, without any additional synchronization on the part of the calling code. The

effect is that operations on a thread-safe object will appear to all threads to occur in a fixed, globally consistent

order.

The relationship between correctness and thread safety is very similar to the relationship between consistency

and isolation used when describing ACID (atomicity, consistency, isolation, and durability) transactions: from the

Page 278: Dev Force 2010 Developers Guide

278 | P a g e

perspective of a given thread, it appears that operations on the object performed by different threads execute

sequentially (albeit in a nondeterministic order) rather than in parallel. 65

The DevForce EntityManager is safe for multithreaded read operations. If you attempt writes to a single

EntityManager from multiple threads, you must synchronize the write operations yourself. For us to

make the EntityManager thread-safe for write operations would require that we make thread-safe every

method therein – and every method of the business objects it manages, including property setters. This

would increase the EntityManager’s complexity – and degrade its performance – significantly. Every

user of the EntityManager, and every use thereof, would incur the performance penalty, whether such

users and uses required thread-safety or not.

At least 90% of the use cases that people submit to us for multi-threading involve retrieving data while

other operations proceed. For this we have provided Asynchronous Queries. You call the

EntityManager’s ExecuteQueryAsync() method, and we take care of putting the data retrieval operation

on a separate thread so that the rest of your application can continue processing. Any number of such

asynchronous queries can be launched simultaneously. Does this mean that you can’t do multi-

threading (other than by using Asynchronous Queries) in a DevForce application? No, it does not. It just

means that you should never share a single EntityManager, or any of the entities it manages in its cache,

across multiple threads. Let us repeat:

Never share a EntityManager across more than one thread.

Never share entities from a given EntityManager in more than one thread.

Note that the problems that occur with multi-threaded applications are, by their very nature, timing-

dependent and difficult to diagnose, reproduce, and test for. Your multi-threaded process can work

successfully for long periods of time, then fail catastrophically when two or more inconsistent changes

happen to be made simultaneously. You should definitely not count on this failure occurring at a

convenient time!

If You’re Determined To Do Multi-Threading…

Be sure you really need multiple threads. Remember, if all you want to do is fetch data asynchronously,

you will be fully satisfied with Asynchronous Queries. Don’t mess around with multi-threading if this is

all you want to do.

Use caution when writing any multi-threaded app. Don't be lulled into a false sense of confidence just

because it is easy to spawn a BackgroundWorker. Multi-threading is still hard. The BackgroundWorker

made the syntax easy: it did not make good multi-threaded design easier!

65 Excerpted from ”Characterizing Thread Safety” by Brian Goetz, available on the web at:

http://www-128.ibm.com/developerworks/java/library/j-jtp09263.html

Page 279: Dev Force 2010 Developers Guide

279 | P a g e

If you’re new to multi-threaded programming, work with someone who has significant prior experience

doing it, if at all possible. If you can’t arrange that, do some serious reading and study on the topic

before attempting it on a critical application.

If your multi-threaded aspirations involve DevForce business objects:

Use a different EntityManager in each thread. Such EntityManagers can do anything a normal

EntityManager can do; they can fetch (both synchronously and asynchronously), save, and so forth.

Never use EntityManager.DefaultManager when multi-threading – the DefaultManager is “global” to the

AppDomain and will be shared among any threads in which it is used.

Never communicate entities across thread boundaries. If the caller must know about some entities, send a

list of EntityKeys across the thread boundary in a call back. Alternatively, you could bury EntitySets in a

call back to serialize copies of entities across the thread boundary.

Batching Asynchronous Tasks

DevForce includes two classes, the AsyncSerialTask and the AsyncParallelTask, that permit you to define

and execute asynchronously, in series or in parallel, a collection of linked actions. Each method uses a

single callback to handle all processing results, and each provides the ability to specify an

ExceptionHandler to provide a single point of error handling.

AsyncSerialTask The AsyncSerialTask provides you with a mechanism to define a sequence of linked actions, each of

which can be performed synchronously or asynchronously. To use the feature, you first create the root

task in the sequence, and then add actions to it, until you have a sequence ready for launch via the

Execute method.

The AsyncSerialTask allows you easily to link a series of actions, passing the output from the previous

action as input to the next action. Without the AsyncSerialTask, you would need to issue each

asynchronous action separately and in the handler for the completed action launch the next action in

the sequence. The AsyncSerialTask takes care of this housekeeping for you. It allows you to pass an

argument into the task sequence when execution begins, and to specify a single handler when the entire

sequence completes. You can specify an ExceptionHandler to provide a single point of error handling.

Note that the entire sequence is not executed as a group on a worker thread. Instead, as each action is

serially executed, if the action is asynchronous then a worker thread is started for it; when the action

completes its results are returned back to the main thread, which then continues with the next action in

the sequence.

Note that if you add only synchronous actions and functions to the AsyncSerialTask the entire sequence

will execute synchronously.

Use the AsyncParallelTask rather than the AsyncSerialTask if you can execute all actions simultaneously.

C#

public void SampleAsyncTask() {

Page 280: Dev Force 2010 Developers Guide

280 | P a g e

DomainModelEntityManager mgr = new DomainModelEntityManager();

// Let's take a series of "actions" all performed synchronously.

// Login - if ok, then:

// - Run a query for customers

// - Modify the retrieved data

// - Save changes

// It might look like this:

if (mgr.Login(new LoginCredential("demo", "demo", "earth"))) {

var customers = mgr.Customers.Where(c => c.Country == "USA").ToList();

customers.ForEach(c => c.Country = "US");

SaveResult sr = mgr.SaveChanges();

Debug.Assert(sr.Ok);

}

// Now assume that some of these actions should be done asynchronously.

// In Silverlight, any actions which go to the BOS - such as query and save -

// must be performed asynchronously. The AsyncSerialTask let's you group

// a series of actions to be performed together. Without this, you would

// need to issue each async call separately, and in the handler for the

// completed action fire off the next action. The AsyncSerialTask does

// this for you.

// The same actions with the AsyncSerialTask:

// - Login asynchronously

// - When login completes, run an async query to retrieve customers

// - When the query completes, modify the retrieved data

// - Save these changes asynchronously

// (You should also use an ExceptionHandler to trap errors, but we've

// removed that to make this sample a bit easier to read.)

// Here's how you might build this task:

AsyncSerialTask.Create("ASimpleTask")

.AddAsyncLogin(mgr, new LoginCredential("demo", "demo", "earth"))

.AddAsyncQuery(loginArgs => mgr.Customers.Where(c => c.Country == "USA"))

.AddAction(fetchArgs => {

var customers = fetchArgs.Result;

customers.ForEach(c => c.Country = "US");

})

.AddAsyncSave(mgr)

.Execute(null, (completionArgs) => {

SaveResult sr = completionArgs.Result.Result;

Debug.Assert(sr.Ok);

});

}

AsyncParallelTask The AsyncParallelTask allows you to create a set of asynchronous actions, execute them in parallel, and

provide a single callback to handle all processing results.

To use the feature, you first create a task, and then add asynchronous actions to it until you have a set

ready for launch via the Execute method.

In the absence of the AsyncParallelTask you would need to issue multiple asynchronous method calls

and provide handlers for each. Instead, the AsyncParallelTask takes care of much of this housekeeping

for you. It allows you to pass an argument to each action in the task, and to specify a single handler

when the entire task completes. You can also specify an ExceptionHandler to provide a single point of

error handling.

Page 281: Dev Force 2010 Developers Guide

281 | P a g e

Each action is executed on a separate worker thread. The completion action is called on the main thread

once all actions have completed. If you've specified a callback for an asychronous action, that callback

will also be called on the main thread. The Execute call returns immediately after starting all of the

specified parallel actions.

Use the AsyncSerialTask rather than the AsyncParallelTask if you need to link the outputs from one

action to the inputs to the next, or to mix asynchronous and synchronous actions.

C#

public void SampleAsyncTask() {

DomainModelEntityManager mgr = new DomainModelEntityManager();

// Let's take a few "actions" performed asynchronously:

// - Run a query for all customers

// - Run a query for all employees

mgr.ExecuteQueryAsync<Customer>(mgr.Customers, cb => {

if (cb.Error != null) {

Debug.WriteLine(cb.Error.Message);

} else {

cb.Result.ForEach(c => Debug.WriteLine(c.CompanyName));

}

}, null);

mgr.ExecuteQueryAsync<Employee>(mgr.Employees, cb => {

if (cb.Error != null) {

Debug.WriteLine(cb.Error.Message);

} else {

cb.Result.ForEach(e => Debug.WriteLine(e.LastName));

}

}, null);

// Since these async actions both essentially run in parallel, let's

// combine them into a single task:

AsyncParallelTask.Create()

.AddExceptionHandler(args => Debug.WriteLine(args.Exception.Message))

.AddAsyncQuery(1, x => mgr.Customers)

.AddAsyncQuery(2, x => mgr.Employees)

.Execute(cb => {

((EntityFetchedEventArgs <Customer>)cb.CompletionMap[1])

.Result.ForEach(c => Debug.WriteLine(c.CompanyName));

((EntityFetchedEventArgs <Employee>)cb.CompletionMap[2])

.Result.ForEach(e => Debug.WriteLine(e.LastName));

});

}

Service Oriented Architecture

We are sometimes asked whether DevForce is a Service Oriented Architecture (SOA).

DevForce applications can be SOA in several respects. First, DevForce applications are .NET applications,

which means it is very easy to build web services into the application. Second, we can expose all or part

of the business object model as a web service, which means external applications and non-.NET clients

can take advantage of the hard work we put into our model.

Page 282: Dev Force 2010 Developers Guide

282 | P a g e

On the other hand, SOA is easy to abuse. It does not belong everywhere and it is an especially

unfortunate choice for cross-tier data transfers in an n-tier application. The difference between n-tier

and Service Oriented Architectures are vitally important and worth at least some discussion such as

we’re about to have now.

SOA design emphasizes loose coupling and a contract between a client and a service. The client should

know as little as possible about the service internals and engage with the service only through a

message-like interface having a simple protocol. The interface should be course grained, meaning that

we expect to get a lot done with each service method call and we don’t over-task the interface with fine

details. We are often aware of the boundary between the client and service.

The services of an SOA application do not belong to that application. In principle, the services are

designed independently of any particular SOA application and could be accessed by any authorized

client.

An n-tier application, by contrast, has logical layers that are tightly coupled. The layers tend to have

many, fine grained interface points. The layers are designed to work together as a single, operating

whole. It is a secondary benefit if a tier can serve another application through the same interface.

SOA proponents emphasize the importance of the contract between client and server. But SOA can only

ensure the consistency of the interface points. It can’t ensure that the semantics implied in the interface

are actually the same on both sides of the fence.

A program manager on Microsoft’s CLR team made an analogous point in a commentary on the difficulty

of choosing between defining classes and interfaces:

I often hear people saying that interfaces specify contracts. I believe this is a dangerous myth. Interfaces, by

themselves, do not specify much beyond the syntax required to use an object. The interface-as-contract myth causes

people to do the wrong thing when trying to separate contracts from implementation, which is a great engineering

practice. Interfaces separate syntax from implementation, which is not that useful, and the myth provides a false

sense of doing the right engineering. In reality, the contract is semantics, and these can actually be nicely expressed

with some implementation.[emphases ours]

Krzysztof Cwalina, [Framework Design, 80]

N-tier applications can impose much stricter contracts than SOA applications. They can enforce common

semantics by requiring both sides to implement the contract by using the same object classes. A

DevForce application forces the type on the server tier to be exactly the same as the type on the client

tier. This is known as “type fidelity”.

Hiding implementation details is as essential to n-tier design as it is to SOA design; each layer should

know as little as possible about the design and works of the other layers. But an n-tier application can

and should impose cross-tier requirements if these help realize application objectives. For example, we

can require that the data access tier communicate with a UI tier via .NET remoting rather than Web

Page 283: Dev Force 2010 Developers Guide

283 | P a g e

services if this makes the application less complex and perform better; such objectives may matter far

more to the customer right now than exposing the business object model as a service66.

An n-tier application can also be an application with a Service Oriented Architecture. The application

may implement some number of features by invoking a Web service or by embedding a Web service

within an object wrapper. The tier may expose some of the application’s own functionality as Web

services. In such cases, the application is communicating externally via the service.

Cross-tier interactions, on the other hand, are communications within the application.

Let not blind obedience to SO orthodoxy triumph over rational choice.

POCO Support in DevForce

In addition to objects based on classes inheriting from IdeaBlade.EntityModel.Entity and generated by

the DevForce OM Extension, , you can also use Plain Old CLR Objects (POCOs) with DevForce. The class

for your object must be deployed on both the client and the server and must be contained in one of the

assemblies routinely searched by DevForce. An additional class (or classes), a “POCO service provider”,

must be supplied server-side, marked with the EnableClientAccess attribute. This class will contain

methods to perform the server-side “CRUD” operations for your custom objects. The names for the

methods may either conform to flexible naming conventions (to be discussed below), or they can be

named anything you desire and marked with an attribute to indicate their purpose. For convenience,

you will probably also want to deploy a client-side class containing extension methods for the

EntityManager, so that you can refer to your POCO objects in LINQ queries in a manner similar to that

which you use with your DevForce Entities.

If the objects have an identified (primary) key value, they will operate fully as first-class citizens in the

DevForce local cache. That means, among other things, that they can be:

Stored in the cache;

Queried against and retrieved from the cache;

Updated in the cache;

Created in the cache; and of course

Saved from the cache.

Objects that do not have an identified key can still be retrieved, but will not be stored in the DevForce

cache.

DevForce’s support for POCO objects follows Microsoft RIA (Rich Internet Application) standards, and

uses RIA attributes and naming conventions, to the extent that those are available. As those standards

66 This decision does not prevent us from exposing the business object model as a Web service later. Even then we could retain

our remoting interface within the application.

Page 284: Dev Force 2010 Developers Guide

284 | P a g e

and facilities evolve or are fleshed out, the implementation in DevForce will be enhanced or migrated to

maintain maximum compatibility with the RIA standards.

Examples of POCO Classes Here is a class representing a State of the United States. At runtime, you might, for example, creating

instances of this class using data from an XML data file deployed on the server:

C#

using System;

using System.Collections.Generic;

using System.Linq;

using System.ComponentModel.DataAnnotations;

namespace AppWithPocos.Pocos {

public class State {

public State() {

}

//[Key]

public string Abbrev {

get {

return _abbrev;

}

set {

_abbrev = value;

}

}

public string Name {

get {

return _name;

}

set {

_name = value;

}

}

public bool Lower49 {

get {

return _lower49;

}

set {

_lower49 = value;

}

}

public long Population {

get {

return _population;

}

set {

__population = value;

}

}

#region Private Fields

string _abbrev;

string _name;

bool _lower49;

long _population;

#endregion Private Fields

}

}

Page 285: Dev Force 2010 Developers Guide

285 | P a g e

Examples of a POCO Service Provider Class This POCO Service Provider class can be named anything – and you may have many such classes – but

must be flagged with the attribute [EnableClientAccess]. All such classes will be deployed and used

server-side only.

C#

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.ComponentModel;

using System.Text;

using IdeaBlade.EntityModel;

using IdeaBlade.Core;

using System.Reflection;

using IdeaBlade.Core.DomainServices;

using AppWithPocos.Pocos;

using System.Xml;

using System.ComponentModel.DataAnnotations;

using System.Runtime.Serialization;

namespace AppWithPocos {

[EnableClientAccess]

public class PocoServiceProvider {

public PocoServiceProvider() { }

#region State

//[RequiresAuthentication] // Uncomment this attribute will require "real"

authentication

public IEnumerable<State> GetStates() {

IEnumerable<State> states = ReadStatesData("states.xml");

return states;

}

private static IEnumerable<State> ReadStatesData(string fileName) {

// Create an isntance of XmlTextReader and call Read method to read the file

XmlTextReader textReader = new XmlTextReader(fileName);

textReader.Read();

List<State> states = new List<State>();

while (textReader.Read()) {

State aState = new State();

textReader.MoveToElement();

if (textReader.Name == "State") {

aState.Abbrev = textReader.GetAttribute("Abbrev").Trim();

aState.Name = textReader.GetAttribute("Name").Trim();

aState.Lower49 =

Convert.ToBoolean(Convert.ToInt32(textReader.GetAttribute("Lower49")));

states.Add(aState);

}

}

return (IEnumerable<State>)states;

}

#endregion State

}

}

Page 286: Dev Force 2010 Developers Guide

286 | P a g e

Note the “GetStates()” method. This method retrieves the data requested in a query (which is usually

submitted from the client). It follows a naming convention similar to the one supported under RIA

services, where the name consists of a prefix that is one of a number of synonyms for “retrieve” (here,

“Get”) and a suffix that is the pluralized name of a POCO type (here, “States”). The prefixes may be any

of the following:

Get Query

Fetch Retrieve

Find Select

When the above naming convention is used, a query can be constructed client-side with an expression

such as the following:

C#

new EntityQuery<State>("States", anEntityManager);

Alternatively, the server method can be adorned with the QueryAttribute.

C#

[Query]

public IEnumerable<State> ReturnAllStates() {

IEnumerable<State> states = ReadStatesData("states.xml");

return states;

}

In that case, when called client-side the exact name used for the query must be supplied:

C#

new EntityQuery<State>("ReturnAllStates", anEntityManager);

Query Methods with Parameters

Methods with parameters are supported as well. For example, an additional overload to the GetStates()

method above that only returns states with a population size greater than a size passed in might be

written as shown below. (Naturally, this would require that the internal ReadStatesData() method be

modified as well.)

C#

public IEnumerable<State> GetStates(long minPopulationSize) {

IEnumerable<State> states = ReadStatesData("states.xml", minPopulationSize);

Page 287: Dev Force 2010 Developers Guide

287 | P a g e

return states;

}

On the client side, parameters may be specified by using one of the EntityQuery.AddParameter()

overloads. The following snippet calls the parameterized GetStates() method to return just those states

with a population greater than one million people:

C#

var query = new EntityQuery<State>("States", anEntityManager);

query.AddParameter(1000000);

// see

Any number of query parameters are permitted, and the standard .NET overload resolution rules apply.

This means that the order and type of the parameters are checked to find appropriate matches, with

type coercion occurring as required.

When to Use Parameterized Query Methods Rather Than LINQ Expressions

The following two queries will return the same results:

C#

var query1 = new EntityQuery<State>("States", anEntityManager);

query.Where(state => state.Population > 1000000);

var query2 = new EntityQuery<State>("States", anEntityManager);

query.AddParameter(1000000);

Furthermore, in both cases, the restriction to those states with greater than one million population

occurs on the server, not the client. So the question arises: is one to be preferred over the other? The

answer usually depends upon how the server-side method itself is implemented.

In general, unless the server-side method can internally use the query parameter to restrict its own

query against some back-end data store, query parameters have no advantage over LINQ query

restrictions. In fact, LINQ queries are far more flexible and intuitive to work with under most

circumstances. Nevertheless, there will be cases where a back-end data store’s ability to optimize some

queries will yield sufficient performance improvement to justify the use of query parameters.

For example, consider the Windows file system’s ability to search for files, given a path and wildcards.

While the same result could be accomplished via a server-side method that returned all of the files in

the file system and then iterated over them to locate a desired set of files, it would likely be faster to call

the file system directly with the path and wildcard restrictions.

Page 288: Dev Force 2010 Developers Guide

288 | P a g e

Example of a Client-Side Class Containing Extension Methods for the

EntityManager The following class, deployed client-side, can provide further help in putting your POCO objects on an

equal footing with your DevForce-generated objects. First, look at the class; then below, we’ll show

what its presence permits in terms of data retrieval syntax:

C#

using System;

using System.Collections.Generic;

using System.Linq;

//using System.Web;

using System.Diagnostics;

using System.Text;

using System.Linq.Expressions;

using IdeaBlade.EntityModel;

using IdeaBlade.EntityModel.Extensions;

using IdeaBlade.Core;

//using AppWithPocos.Pocos;

using DomainModel;

using AppWithPocos.Pocos;

namespace AppWithPocos {

public static class EmExtensions {

#region State

public static EntityQuery<State> States(this EntityManager em) {

return new EntityQuery<State>("States", em);

}

#endregion State

}

}

Because there are now static property named “States” and “Foos” available that return

EntityQuery<State> and EntityQuery<Foo>, respectively, you can now order the retrieval of States and

Foos with the following syntax:

C#

_mgr.ExecuteQueryAsync<State>(_mgr.States()

.Where(s => s.Lower49, GotStates, null);

This is very similar to what you do with DevForce entities, except that, since States() is an extension

method, so you will have to include the parentheses -- _mgr.States() -- which you do not have to do

when referencing the generated properties of the EntityManager that return EntityQuery<T>.

Page 289: Dev Force 2010 Developers Guide

289 | P a g e

Note that you can extend the queries with clauses with the full set of LINQ extension methods -- such as

Where(), used above -- just as you can do with ordinary queries.67

Obtaining an EntityAspect Property on Your POCO Object Objects from DevForce-generated Entity classes have an EntityAspect property through which a number

of important operations and useful pieces of metadata can be obtained. Custom objects can benefit

from the same facilities by one of two methods:

Implementing a very simple interface, IHasEntityAspect, or

Wrapping them at runtime

The latter method preserves their DevForce-ignorant POCO status, but will not provide equal

performance with the former method.

Getter and Setter can be placeholders in the IHasEntityAspect implementation:

C#

namespace AppWithPocos.Pocos {

public class State:IHasEntityAspect {

…[snip]

#region IHasEntityAspect Members

public EntityAspect EntityAspect {

get;

set;

}

#endregion

To wrap an Entity at runtime for the purpose of getting at the EntityAspect property, call the static

method EntityWrapper.Wrap():

C#

var stateWithAspect = EntityWrapper.Wrap(aState);

Having done that, you can then ask questions like the following…

C#

Bool isModified = stateWithAspect.EntityAspect.IsModified();

…and of course, use any of the other facilities of EntityAspect. (Those are documented in the

Developers Guide chapter “Class Libraries”.)

67 As of DevForce 5.1.1, the Include() syntax on a POCO entity query is not yet implemented. The call will compile but will not

yet do anything. This will be corrected in a later release.

Page 290: Dev Force 2010 Developers Guide

290 | P a g e

Data Contract Serializer (DCS) versus .NET Data Contract Serializer

(NDCS) The .NET framework includes several important serialization classes. For the purpose of your DevForce

apps, two of these are of particular importance: the Data Contract Serializer (DCS) and the .NET Data

Contract Serializer (NDCS). DevForce will use one of these serializers in two situations: 1) in transmitting

requests and data in an n-tier application, and 2) when saving and restoring the EntityCacheState.

By default, DevForce uses the DataContractSerializer (DCS). This has important implications, since this

serializer must be told in advance which types will be serialized, i.e., the “known types” of the

application. In WinClient applications, you may instead use the NetDataContractSerializer (NDCS), by

changing a setting in the IdeaBladeConfig.

Your POCO classes need to be serializable, just as DevForce entities are by default. Your POCO classes

also need to be identified as “known types” when using DCS, so that the serializer can use them. If your

POCO class contains a KeyAttribute, DevForce will automatically identify the class as a known type.

Here’s a POCO class that does have key:

C#

public class State {

public State() {

}

[Key] public string Abbrev {

get {

return _abbrev;

}

set {

_abbrev = value;

}

}

public string Name {

get {

return _name;

}

set {

_name = value;

}

}

}

VB

When your class is defined without a key, you need to explicitly tell DevForce that the type should be

identified to the serializer as a known type. There are several ways to do this; one way is to attribute

the class with the DevForce DiscoverableType attribute:

C# Using IdeaBlade.EntityModel;

[DiscoverableType(DiscoverableTypeMode.KnownType)] public class State {

public State() {

Page 291: Dev Force 2010 Developers Guide

291 | P a g e

}

public string Abbrev {

get {

return _abbrev;

}

set {

_abbrev = value;

}

}

public string Name {

get {

return _name;

}

set {

_name = value;

}

}

}

VB

A second method is to implement the (empty) interface IKnownType:

C# Using IdeaBlade.EntityModel;

public class State {

public State() : IKnownType { }

public string Abbrev {

get {

return _abbrev;

}

set {

_abbrev = value;

}

}

public string Name {

get {

return _name;

}

set {

_name = value;

}

}

}

The first method is preferable, but the second method can be useful if you will have many classes that

inherit from a base class, and you wish only to “mark” the base class.

A third option is to implement the DevForce IKnownTypeProvider interface and return your list of known

types:

Page 292: Dev Force 2010 Developers Guide

292 | P a g e

C#

public class KnownTypeProvider : IKnownTypeProvider {

public IEnumerable<Type> AddKnownTypes() {

var list = new Type[] { typeof(OrderRequestInfo),

typeof(OrderResponseInfo),

typeof(List<long>)

};

return list;

}

}

Attributing a Type Where the Signature of Any DataMember-Attributed Member

Indicates a Different Type Than Is Actually Returned

The KnownType attribute is used by the DCS to mark up a type with information about any of its

serializable members where the declared type of the member is different from the actual type of the

member.

Suppose, for example, that your State class includes a Cities property which is typed as an object but

actually returns a List<Cities>. This will confuse the serializer and make it serialize the property

improperly. To enable the property to be serialized properly, you must attribute it as follows:

C#

[DataContract]

[KnownType(typeof(List<City>)] public class State {

public State() {}

[... snip]

[DataMember]

public object Cities { get {

return _cities;

}

set {

_cities = value;

}

}

#region Private Fields

List<City> _cities;

#endregion Private Fields

}

Page 293: Dev Force 2010 Developers Guide

293 | P a g e

POCO Save mechanisms DevForce provides two different mechanisms for saving POCO objects. These will be referred to as

either adapter-based or convention-based implementations. By default, DevForce will attempt to locate

an adapter-based implementation; if unsuccessful, it will then look for a convention-based

implementation.

The implementation mechanism can also be specified explicitly by setting the PocoSaveMode property

of the SaveOptions instance passed into the EntityManager.SaveChanges call:

C# var so = new SaveOptions();

so.PocoSaveMode = PocoSaveMode.UseEntitySaveAdapter;

myEntityManager.SaveChanges(so);

Page 294: Dev Force 2010 Developers Guide

294 | P a g e

The PocoSaveMode is an enumeration with the following structure:

C#

[/// <summary>

/// Determines how to discover any custom server side POCO save methods.

/// </summary>

public enum PocoSaveMode {

/// <summary>

/// Use an EntitySaveAdapter subclass if found, otherwise use save methods discovered

via convention.

/// </summary>

Default = 0,

/// <summary>

///

/// </summary>

UseEntitySaveAdapter = 1,

/// <summary>

///

/// </summary>

UseMethodsDiscoveredViaConvention = 2

}

Adapter-Based Saves

The adapter-based mechanism requires the existence of a server-side class that inherits from the

IdeaBlade.EntityModel.Server.EntityServerPocoSaveAdapter. If such a class is found, a single instance of

this class will be created for each SaveChanges call, and the appropriate methods corresponding to

each insert, update, or delete will be called for each entity to be saved. A single null-parameter

constructor is required.

Note that a single insert, update, or delete method handles saves for every entity type, so if save logic

needs to be different for different types, the type of the entity passed in will need to be inspected and

the execution branched appropriately.

Page 295: Dev Force 2010 Developers Guide

295 | P a g e

The following bare-bones version of an EntitySaveAdapter implementation shows the methods you have

available to override:

C#

public class PocoSaveAdapter : EntityServerPocoSaveAdapter

{

public PocoSaveAdapter() {

}

public override void BeforeSave(

System.Collections.IEnumerable entities, SaveOptions saveOptions) {

}

public override void AfterSave() {

}

public override void InsertEntity(object entity) {

}

public override void UpdateEntity(object entity, object originalEntity) {

}

public override void DeleteEntity(object entity) {

}

}

Convention-Based Saves

The convention-based mechanism requires the existence of a server-side class with the

[EnableClientAccess] attribute. This class will include insert, update, and delete methods named

according to the conventions defined below. It must also include a single null-parameter constructor.

Page 296: Dev Force 2010 Developers Guide

296 | P a g e

If such a class is found, a single instance of it will be created for each SaveChanges call, and the

appropriate methods corresponding to each insert, update, or delete will be called for each entity to be

saved.

For each type for which a persistence operation is provided, up to three methods must be written. The

name of the method must begin with one of the prefixes defined below. The method must also conform

to the signature defined below.

If you do not expect to save objects that require one or more of these signatures, then they do not have

to be implemented. In other words, if you never plan to delete “Foo” type objects, then you do not

need to implement the Delete method.

Insert Method

Prefixes: Insert, Add, or Create

Signature: void InsertX(T entity), where T is an entity Type

Update Method

Prefixes: Update, Change, or Modify

Signature: void UpdateX(T current, T original), where T is an entity Type

Delete Method

Prefixes: Delete or Remove

Signature: void DeleteX(T entity), where T is an entity Type

Below is an example implemention showing the use of the required conventional method names and

signatures. InsertFoo could equally be named “AddFoo” or “CreateFoo”; UpdateFoo could be named

“ChangeFoo” or “ModifyFoo”; and so forth.

Page 297: Dev Force 2010 Developers Guide

297 | P a g e

C# [EnableClientAccess]

public class PocoSaveAdapterUsingConventions {

public PocoSaveAdapterUsingConventions() {

}

public void InsertFoo(Foo entity) {

// insert logic for any „Foo‟ entities

}

public void InsertBar(Bar entity) {

// insert logic for any „Bar‟ entities

}

public void UpdateFoo(Foo current, Foo original) {

// update logic for any “Foo‟ entities

}

public void UpdateBar(Bar current, Bar original) {

// update logic for any “Bar‟ entities

}

public void DeleteFoo(Foo entity) {

// update logic for any “Foo‟ entities

}

public void DeleteBar(Bar entity) {

// update logic for any “Bar‟ entities

}

Page 298: Dev Force 2010 Developers Guide

298 | P a g e

}

Summary – Things to Remember When Using POCOs in Your DevForce

App POCO classes must be included in both the server- and client-side assemblies. Put them in the server-side

project and link them into the client-side project.

Include a POCO ServiceProvider class server-side.

Include EntityManager extensions client-side.

You can use all forms of LINQ query against these objects.

If your POCO class has a primary key (designated with the [Key] attribute, instances of it will be stored in

the EntityManager cache, and can be updated, deleted, or inserted there.

If your POCO class has no key, instances can be retrieved but will not be placed in the EntityManager

cache.

Implement IHasEntityAspect to get EntityAspect property and associated functionality on your “almost

POCO” entity.

Page 299: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

Validation Through Verification

Validation Through Verification ............................................................................................... 299

Introduction ....................................................................................................................................................... 299

DevForce Verification ....................................................................................................................................... 300

Getting Started ..................................................................................................................................................... 301

Validation-Related Settings In the Entity Data Model Designer ....................................................................... 301

Generated Property Code ................................................................................................................................... 302

A (Very) Brief Overview of Verification Mechanics ......................................................................................... 306

VerifierOptions .................................................................................................................................................... 306

Verification in the User Interface ....................................................................................................................... 311

Verification in WPF and Silverlight .................................................................................................................. 311

Verification and WinForm User Interfaces ........................................................................................................ 311

Introduction “Validation” is the process of evaluating input and judging it valid or invalid. Such evaluation subjects

the input to a battery of “validation rules” that evaluate the input in the appropriate context. For

example, if the user enters a “committed delivery date” we might want to ensure that:

the committed delivery date is reasonable in the abstract, e.g., occurs in the future;

it is possible to deliver on that date given the availability of the desired products, and the currently

selected shipping method, and whether there is enough time to prepare the goods for shipping;

the order is “shippable”, e.g. the customer‟s credit has been verified, the address is legitimate, and the

total is within the limits authorized for this user.

Clearly such rules can be complex, involving not only the input value itself but also the state of the

target object (the order), facts about related objects (customer, shipper, product), and aspects of the

environment during the validation (time of day, the user’s role).

User input validation gets most of the attention but we need to validate programmatic inputs as well.

That delivery date could as easily be set by business logic or a web service request as it is by a wayward

click on a calendar control. The rules are the same for everyone, human and machine.

Validation is hard to do well especially as the application grows and validation criteria change. Common failings

include:

Missing and incorrect validity checks

Page 300: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

Inconsistent checking

Failure to validate at the right times

Poor communication with end-users

Inadequate mechanisms for correcting mistakes.

Enterprise application developers are looking for a robust validation system that operates consistently

and reliably across a large application. Robust validation cuts both “vertically” and “horizontally”:

We validate “vertically” when we validate several times in multiple layers of the application. We want to

validate in the client UI layer so we can give immediate feedback to the user. We may need to validate

again when we save, even though the objects we save are no longer on screen. We may even need to

validate again on the server side to protect against misadventure coming from outside the relative safety of

the hosted environment.

We validate “horizontally” when we apply the same mechanisms uniformly across all modules of the

application. If the user can set the delivery date on any of several screens, the same rules ought to apply –

unless, of course, there is something special about a particular screen.

DevForce Verification

“Verification” is IdeaBlade‟s answer to the challenges of validation. “Verification” is a collection of interoperating

validation components that are both easy to use and capable of handling sophisticated scenarios. The developer can:

Write rules of any complexity. The developer can draw upon pre-defined rules (required value, range

check, field length) or write custom rules of any complexity, including rules that compare multiple

fields and span multiple objects.

Generate validity checking into business objects automatically via the DevForce “Object Mapper.

Validate any kind of object, not just objects that derive from base business classes.

Trigger validity checking at any time such as upon display, before save, or when setting properties. The

engine can fire “pre-set” to block critically errant data from entering the object or fire “post-set” to

accommodate temporarily invalid values. The UI can inspect the engine for rules of interest, fire them,

and adjust the display accordingly. It could color a text box, for example, or hide a portion of the form

until applicable criteria were met.

Display a localized message in the UI without special programming. The UI could display just the

“validation failed” message but it might also show warnings or “ok” messages and it might supplement

the message be re-directing the application focus to the offending object and property. Each rule returns

a rich, extensible object with all the information necessary for the developer to deliver a helpful

response.

Discover rules in the code or retrieve them at runtime from a central store. The engine automatically

discovers rules in the code and can acquire rules defined externally in configuration XML, a database,

or some other store of rules. The application can inspect, add, and remove rules at any time.

Leverage rules inheritance. Rules defined in base classes propagate to their derived classes where they

are “inherited” or overridden.

Adjust validation behavior based on a custom validation context. The developer must have the

flexibility to convey custom information to the validation process to cope with the variety of situational

factors that arise in real applications.

Inspect and intervene as the engine validates. The application can monitor the engine‟s progress and

interrupt, modify, or terminate a validation run at any point.

Page 301: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

“Verification” Versus “Validation”

The DevForce validation mechanism is called “Verification” and all of its components are named with

some variation on this word. We mean to try neither your patience nor your vocabulary. We would call

our offering “validation” if we could.

However, Microsoft uses the term “validation” throughout .NET. It appears in Windows Presentation

Foundation (WPF) and Windows Workflow Foundation (WWF) namespaces and in the Enterprise Library

as well. Microsoft also uses the following class names:

ValidationError, ValidationErrorCollection, ValidationManager, ValidationResult,

ValidationRule, ValidationStatus, ValidationType

IdeaBlade is integrating DevForce with Microsoft’s WPF and WWF. You are likely doing the same. We

will all become confused if we cannot easily distinguish among the same or very similar names.

So “Verification” it is. We will continue to say “validation” when we speaking in general terms; we will

use the term “verification” (and its variants) when we refer specifically to the DevForce classes located

in the IdeaBlade.Verification namespace.

Whither WPF Validation?

We can’t leave this digression without a parting comment about validation in Microsoft’s Windows

Presentation Foundation.

WPF validation concentrates on presentation of validation results within a WPF user interface. This is a

vital aspect of any validation strategy. At present, most applications punish the user for the developer’s

own design failings. We need better UIs and better means to guide users rather than humiliate them.

DevForce’s “verification” concentrates on the validation process. It complements WPF by producing the

rich validation results necessary to deliver an effective user experience. We will address the integration

of these mechanisms in a separate document.

Getting Started

The easiest point of entry to DevForce verification is through the DevForce Object Mapper.

We‟ll assume that you are familiar with the Object Mapper and have an existing, working application with

its own business object model. See the “Hello DevForce” topic document in the DevForce Learning

Resources under “Introduction to DevForce” for a walk-through of the Object Mapper.

Validation-Related Settings In the Entity Data Model Designer

1. Open an Entity Data Model in the Visual Studio Entity Data Model designer.

2. Display the Properties panel and then click in white space in the designer window to display the

properties of the ConceptualEntityModel. Note the two validation-related properties:

Generate Validation Attributes

Page 302: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

Generate Verification Attributes

The “Validation Attributes” refer to the .NET validation attributes – those used by Silverlight.

The “Verification Attributes” refer to the DevForce verification attributes – used by DevForce

to control the behavior of its verification types.

3. You can control whether verification is performed before proposed new values are pushed into a

business object property, after this is done, or both by means of a VerifierOptions type. An instance of

the type is available as a property on the VerifierEngine class via DefaultVerifierOptions; and also on

the Verifier, VerifierArgs, and VerifierResults classes as VerifierOptions.

Generated Property Code Here we’ll show you the results of the four possible combinations of values for the two validation-

related code generation properties.

We recommend that you use only use DevForce verification attributes (the first combination documented

below) unless you have a compelling reason to do otherwise (e.g., you have a large amount of code that

already uses the .NET attributes and facilities). DevForce verification is a superset of the .NET variety, and

it will be less confusing to your development team if only one style of attribute is used.

Generate (DevForce) Verification Attributes Only

Page 303: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

Here is the FirstName property of an Employee object as generated with the settings shown above:

C# #region CompanyName property /// <summary>Gets or sets the CompanyName. </summary> [Bindable(true, BindingDirection.TwoWay)] [Editable(true)] [Display(Name="CompanyName", AutoGenerateField=true)] [IbVal.ValidateProperty]

[IbVal.StringLengthVerifier(MaxValue=40, IsRequired=true, ErrorMessageResourceName="Customer_CompanyName")]

[DataMember] public string CompanyName { get { return PropertyMetadata.CompanyName.GetValue(this); } set { PropertyMetadata.CompanyName.SetValue(this, value); } } #endregion CompanyName property

IbVal is an alias for the IdeaBlade.Validation namespace, defined at the top of the code file. The

IbVal.ValidateProperty attribute tells both DevForce and Silverlight to check for verifiers when this

property gets set, and to run any whose settings define them as appropriate. The

IbVal.StringLengthVerifier sets a maximum length on the (text) value, and its IsRequired argument

declares the property non-nullable.

Generate (.NET) Validation Attributes Only

Page 304: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

Here is the generated code that the above settings in the EDM designer:

C# #region CompanyName property /// <summary>Gets or sets the CompanyName. </summary> [Bindable(true, BindingDirection.TwoWay)] [Editable(true)] [Display(Name="CompanyName", AutoGenerateField=true)] [IbVal.ValidateProperty]

[Required()] [StringLength(40)] [DataMember] public string CompanyName { get { return PropertyMetadata.CompanyName.GetValue(this); } set { PropertyMetadata.CompanyName.SetValue(this, value); } } #endregion CompanyName property

DevForce still generates the IbVal.ValidateProperty attribute to tell both DevForce and Silverlight to check for

verifiers when this property gets set, and to run any whose settings define them as appropriate. However, the

non-nullability (i.e., Required) and string length constraints are specified a bit differently than was done when

using DevForce attributes.

DevForce can make use of either style of validation attribute. Its own versions provide richer capabilities than

the .NET counterparts, but if you need your code to use the .NET attributes for reasons of your own, DevForce

cooperates.

Generate Both (DevForce) Verification and (.NET) Validation Attributes

This combination of settings actually causes exactly the same code to be generated as choosing to

Generate (DevForce) Verification Attributes alone does. That’s because we’ve been able to coerce

Silverlight into respecting our attributes (as we do theirs), so that generating their attributes is

actually not necessary in order to use the .NET validation facilities and get them to work as you want

them to.

Page 305: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

C# #region CompanyName property /// <summary>Gets or sets the CompanyName. </summary> [Bindable(true, BindingDirection.TwoWay)] [Editable(true)] [Display(Name="CompanyName", AutoGenerateField=true)] [IbVal.ValidateProperty] [IbVal.StringLengthVerifier(MaxValue=40, IsRequired=true, ErrorMessageResourceName="Customer_CompanyName")] [DataMember] public string CompanyName { get { return PropertyMetadata.CompanyName.GetValue(this); } set { PropertyMetadata.CompanyName.SetValue(this, value); } } #endregion CompanyName property

Generate Neither (DevForce) Verification Nor (.NET) Validation Attributes

Here is the generated code we get after setting both “Generate Validation Attributes” and

“Generate Verification Attributes” to false:

C#

#region CompanyName property /// <summary>Gets or sets the CompanyName. </summary> [Bindable(true, BindingDirection.TwoWay)] [Editable(true)] [Display(Name="CompanyName", AutoGenerateField=true)] [IbVal.ValidateProperty] [DataMember] public string CompanyName { get { return PropertyMetadata.CompanyName.GetValue(this); } set { PropertyMetadata.CompanyName.SetValue(this, value); } } #endregion CompanyName property

DevForce still indicates (via the [IbVal.ValidationProperty] attribute) that validation should be run

against the property; so if you define any custom verifiers using other mechanisms (to be discussed

shortly) they will get exercised. However, there is now no indication from the property attributes that

the property either requires a value, or is limited to any particular length.

Page 306: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

A (Very) Brief Overview of Verification Mechanics

What you’ve seen thus far in this document are verifiers defined with property attributes. You get these

for free with DevForce. Simply by flipping a switch in the Entity Data Model Designer (actually, it’s set

ON by default!) you get all of the basic constraints defined on column values by the backing database

carried through to your business classes in the code that the IdeaBlade OM Designer Extension

generates.

This, however, is by no means the extent of the validation capabilities that DevForce provides. We’ll

describe the Verification system in detail later in this document, but first, so you don’t lose the forest for

the trees, let’s take a moment for a high-level overview of how the system works, what you can do with

it, and how you do it.

Creating a Verifiers Collection for a Type

When one of your business types is first instantiated during an application session, a VerifierEngine is

invoked to discover all verifiers applicable to that type. Some of these verifiers are defined using

property attributes, as you’ve seen; but verifiers may also be defined in .NET code -- in very flexible and

powerful ways! – and even in XML. The VerifierEngine discovers all of the verifiers, however encoded,

and creates an in-memory collection of them. They are thenceforward available for it to call upon

whenever needed.

Execution Modes for a Verifier

Each of the verifiers has its own properties which tell the VerifierEngine when it should be run. For

example, you can define a verifier so that it runs before a proposed new property value is pushed into

the business object; or after; or even both (though that is unusual). You also want most verifiers to run

whenever an entire instance of a type is being validated. To specify these things, you specify the

ExecutionModes on an instance of the VerifierOptions type.

VerifierOptions

The VerifierOptions type collects a number of settings that affect a verifier’s behavior, such as when it it

is run and how it handles exceptions. An instance of the type is available as a property on the

VerifierEngine class via DefaultVerifierOptions; and also on the Verifier, VerifierArgs, and VerifierResults

classes as VerifierOptions.

The properties of VerifierOptions are detailed in the folowing table:

Page 307: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

VerifierOptions has the following properties:

Property Description System Default Value

ErrorContinuationMode Gets or sets whether a failure in this verifier should stop

the execution of the remainder of the batch in which this

verifier is executing. [Enum]

VerifierErrorContinuationMode.Continue

ErrorNotificationMode Used to determine whether the Entity should throw

errors during a property verification or raise the errors

thru the INotifyDataErrorInfo interface instead. [Enum]

VerifierErrorNotificationMode.Notify

ExecutionModes Gets or sets the conditions under which a verifier is

executed. See the material immediately below the table

for more information about these. [Enum]

VerifierExecutionModes.InstanceAndOnAfterSetTriggers

RawVerifierOptions Returns a version of this VerifierOptions with its raw

values. May be used to determine which properties are

inherited. [Enum]

ShouldExitOnBeforeSetError [bool] false

TreatWarningsAsErrors Whether to treat verifier warnings as errors. [bool] false

Verifier [Verifier]

Page 308: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

308 | P a g e

The system default values shown in the table above are the default settings for the properties of

VerifierEngine.DefaultVerifierOptions.

All VerifierOptions properties can be inherited from a parent class and, by default, do so. VerifierEngine

is parent to any Verifiers contained within it; Verifier is parent to any VerifierResults resulting from its

execution. If VerifierEngine.DefaultVerifierOptions is changed, then by default, every Verifier and

VerifierResult will inherit this change. But since Verifier and VerifierResult have their own VerifierOptions

property, any aspect of VerifierOptions can be overwritten for any given Verifier or VerifierResult.

All enum-valued properties on VerifierOptions may be set to an enum value of Inherit to indicate

inheritance. Properties that are of type bool? may be set to null.

More Detail on ExecutionModes

The determination of when and how a verifier is executed is controlled via the

IdeaBlade.Validation.VerifierOptions.ExecutionModes and IdeaBlade.Validation.Verifier.TriggerLinks

properties on each verifier. A verifier can be executed either

1. in response to a single change (e.g., of a property value); or

2. in the context of verifying an entire object.

The first type of verification listed above is known as a triggered verification. The second is known as an

instance verification.

We would apply a triggered verification when (for example) a user was changing, or had just changed,

the HireDate on an employee. At that time we would only want to run those verification tests whose

outcome we feel may have been affected by this specific change. Our goal would be to provide the end

user with instant feedback about their change. In most cases, it will never be easier for them to correct a

mistake than immediately after making it!

Triggered verifications can be subdivided into before and after triggering categories. A BeforeSet

verification is applied before a proposed new property value is actually pushed into the business object.

It prevents invalid data from ever getting into the business object. An AfterSet verification is applied

after the new value is pushed into the business object. It lets the change go in, but raises an immediate

flag.

Instance verification describes an operation that completely verifies an entire entity, applying all

relevant validation rules, whether those rules apply to individual properties of that entity, to a

combination of its properties (the validity of whose values must be assessed in relation to each other),

or in some other way to the entity as a whole.

We might perform an instance verification, for example, on a particular instance of an Employee before

permitting it to be saved to the database. This would likely require performing verification tests on a

number of individual properties of the employee object; it might also require testing the state of related

objects (such as the Orders written by that Employee). Only if the entire suite of tests required to fully

validate the Employee were passed would we give the okay to save it to the database.

Page 309: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

309 | P a g e

Note that instance verification would be unnecessary if we could really be sure that every change

affecting our Employee would be addressed by an appropriate set of triggered verifications. In practice

this can be very difficult, or even impossible, to ensure. What if, for example, our Employee is changed

by some other application which doesn’t apply the same rules that our does? What if it is changed

directly in the database by someone with access to that? Even when we can guarantee that neither of

those things happen, the mechanisms by which a given entity can be changed inside a single application

can become quite complex over time. For all of those reasons, developers commonly perform instance

verification at such key junctures as when an entity is submitted for saving. It’s an important last line of

defense against invalid data.

It is common for a given verifier to be applicable during instance verification as well as during triggered

verification. Every verifier comes with information about the situations in which it should be executed,

via its VerifierOptions.ExecutionModes property. That property takes values from a

VerifierExecutionModes enumeration whose choices permit you to make your verifier run in any and all

of the circumstances you deem appropriate. The values in the VerifierExecutionModes enumeration, and

their impacts, are as follows:

Enum Value Instruction to the VerifierEngine

All68 Run during instance verification and in response to designated

triggers (both before the proposed value is pushed into the business

object, and also after it has been pushed into the business object).

Disabled Do not run.

Inherit Inherit the ExecutionModes setting from the type’s parent (Verifier

if a VerifierResult, and VerifierEngine if a Verifier)

Instance Run during instance verification.

InstanceAndOnAfterSetTriggers Run during instance verification and in response to designated

triggers, after the proposed value has been pushed into the business

object.

InstanceAndOnBeforeSetTriggers Run during instance verification and in response to designated

triggers, before the proposed value is pushed into the business

object.

OnAfterSetTriggers Run in response to designated triggers, after the proposed value has

been pushed into the business object.

OnBeforeSetTriggers Run in response to designated triggers, before the proposed value is

pushed into the business object.

68 This setting is somewhat unusual, but because a verifier has access to information about the triggering context in which it is

being run (i.e., before or after), it is possible to include within a single verifier logic that will only be applied in one context

or another. From this arises the possibility that you, the developer, might want it to run in both triggering contexts.

Page 310: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

310 | P a g e

Settings of InstanceAndOnAfterSetTriggers and InstanceAndOnBeforeSetTriggers are by far this most

common (after Inherit). All is a particularly uncommon setting because it orders triggered execution both

before and immediately after a new value is pushed into a business object. However, a verifier, as it so

happens, has access to information about the triggering context in which it is being run (i.e., before or

after), so it is possible to include within a single verifier logic that will only be applied in one context or

another. From this arises the possibility that you, the developer, might want such a verifier to run in

both triggering contexts.

BeforeSet Versus AfterSet Execution

This topic deserves special discussion, as the choice between these two execution modes can be one of

your most difficult ones.

All other things being equal, it is desirable never to allow invalid values into a business object in the first

place. If all things were equal, one would use OnBeforeSetTriggers or InstanceAndOnBeforeSetTriggers

as the ExecutionModes for all property-level verifiers. However, in practice, these settings can cause

problems, especially before an application has been fully fleshed out. For example, in the user interface,

a BeforeSet verifier can demand that the end user fix a bad value entered for a property right then and

there, before moving on to any other work -- even if she hasn’t the faintest idea what to change the bad

value to. There are ways to handle such issues in a manner that’s friendly to the end user; but in order

not to force you to solve such problems right out of the chute we have set the default value for

ExecutionModes to VerifierExecutionModes.InstanceAndOnAfterSetTriggers.

Defining Triggers for a Verifier

Consider a constraint that specifies that the HireDate for an Employee must be later than the

Employee’s BirthDate. That seems a pretty reasonable requirement, but suppose when the user enters a

HireDate of today, this rule is found to have been violated. But the Employee really was hired today, and

the problem is that the BirthDate previously entered for the Employee was in error, specifying the year

2015 when it should have said 1985. If we prevent the new HireDate value from being entered into that

property, we’ll subject the user to a lot of unnecessary work. She’ll have to clear the new HireDate, even

though it is entirely correct, and then go fix the BirthDate value, and then come back and re-enter the

new HireDate value, even though she got it right the first time. Users don’t have a lot of tolerance for

this sort of thing! It’s confusing, irritating, or often both.

A verifier that applies to BirthDate doesn’t have to be triggered by a change to that property. You can,

instead – or additionally – specify that you want it called when the value of HireDate is changed. You can

even set it to be triggered by a change to a property of some other type; and not just any instance of

that type, but one that happens to be specifically related. For example, suppose you have a rule that

says that the OrderDate on an Order taken by Employee X must be greater than or equal to that

Employee’s HireDate. You could define this rule (as a verifier) on the Employee type, but specify that it

should be triggered not only by a change to Employee.HireDate, but equally by a change to the

OrderDate property of any Order written by that Employee!

Page 311: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

311 | P a g e

Pre-Defined Verifiers

DevForce ships with a number of pre-defined verifiers that can be subclassed in flexible ways: examples

include the DateTimeRangeVerifier, the PhoneNumberVerifier, the NamedRegexPatternVerifier, the

StringLengthVerifier, the RequiredValueVerifier, and a number of others. In addition, it defines a generic

DelegateVerifier<T> that provides unlimited verification capabilities: you can use it to define any sort of

rule you need or can imagine. You’ll find examples of all of these types of verifiers in the sample code

solutions included in the Validation area of the Learning Resources.

Verification in the User Interface

Now that the application is detecting invalid data and throwing exceptions, we had better think about

how we want to handle those exceptions and tell the user what is going on.

Verification in WPF and Silverlight IdeaBlade.Core.ComponentModel.INotifyDataErrorInfo has been implemented in DF 2010 with the same

semantics as the similarly named class in the Silverlight CLR version of System.ComponentModel. This

interface, in turn, is implemented by the DevForce EntityWrapper, from which all of your DevForce-

generated Entity types derive.

Because of the implementation of this interface by Entity, it is now possible for you to configure your

entities to collect validation errors rather than throw exceptions. (You may also do both, if you wish.)

Verification and WinForm User Interfaces

The following material is specific to WinForm user interfaces.

UI Lockup

The UI is going to lock up the moment the user enters an invalid value into a verified UI control. That is

any data entry control: TextBox, DataPicker, ComboBox, etc. The user will not be able to leave that control

until she enters a value that passes validation – not even to close the form.

In this illustration, the user cleared the “Last Name”. The last name is required. The form displays an

error bullet and prevents the user from moving out of the textbox.

How does the user recover?

Page 312: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

312 | P a g e

If this were a grid, she could press the *Esc+ key; it is “standard” for grid controls to restore the previous

value when the user presses “escape.” How many users know that? In any case, this TextBox is not in a

grid and pressing [Esc] does nothing but ring an annoying bell.

The user can press the standard key chord for “undo”: Ctrl+Z. How many users know that?

No, the most users will just keep entering new values until they find one that lets them out of the field.

Needless to say, a UI should apply the “lock up” enforcement technique sparingly. In the author’s

opinion, it makes sense only for

a value the user must know and is sure to know

a value that must be correct immediately and at all times.

Dosage of a dangerous prescription drug would fit this bill. Few other properties qualify.

Unlock the UI with AutoValidate

Recall that the DevForce Entity.BeforeSetValue and Entity.AfterSetValue methods raise a

VerifierResultException when the property fails validation. This exception bubbles up and out of the

property setter.69

Data binding traps the exception70 and responds by locking up the form. Fortunately, WinForms .NET 2.0

makes it easy to change this response.

The key is the System.Windows.Forms.UserControl.AutoValidate property which takes one of the

System.Windows.Forms.AutoValidate enumerations.

AutoValidate Description

Inherit Do what the parent UserControl does. The parent is the UserControl

that contains this UserControl.

This is the default for new UserControl instances.

If there is no parent, the value is the default, EnablePreventFocusChange.

EnablePreventFocusChange Prevents the user from leaving the control until the value passes

validation.

EnableAllowFocusChange Validate but permit the user to leave the control if validation fails.

Disable Does not validate. Generally not a good choice.

Inherit is the default value for all new UserControls71. Inherit means that the UserControl is

governed by the AutoValidate setting of its parent UserControls, the UserControl that contains it.

69 Thanks to the System.Diagnostics.DebuggerNonUserCodeAttribute that decorates the setter.

70 During the data binding Validate event raised when the user attempts to leave the TextBox.

71 UserControl is the base class for developer designed screens. System.Windows.Form inherits from UserControl.

Individual “UI widgets” such as TextBox do not inherit from UserControl.

Page 313: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

313 | P a g e

The outer UserControl, typically a Form, doesn’t have a parent so it is governed by the

EnablePreventFocusChange setting.

If we never change the AutoValidate property on any UserControl, our application is governed by the

setting in the Form which, as we have seen, is EnablePreventFocusChange, the setting that locks up the

form. All UserControls within the Form are inheriting this behavior.

If we change the Form’s AutoValidate property to EnableAllowFocusChange, the widgets on the Form

will no longer lock up when the setter throws an exception. Neither will widgets on the contained

UserControls because they inherit the parent Form’s setting.

So the quick answer to UI lockup:

Change the Form‟s AutoValidate property to EnableAllowFocusChange

C#

this.AutoValidate = System.Windows.Forms.AutoValidate.EnableAllowFocusChange; // Can move

Visual Basic

me.AutoValidate = _ System.Windows.Forms.AutoValidate.EnableAllowFocusChange ' Can move

Improving the User‟s Experience

EnableAllowFocusChange and BeforeSet Triggers

AutoValidate.EnableAllowFocusChange works great for property verifiers governed by BeforeSet

triggers.

The user can move out of the TextBox. Yet she can still see the error bullet protesting the lack of a “last name”.

The TextBox remains cleared so we can see that there is a problem – or rather that there was a

problem, that our intent to clear the name was invalid.

The LastName property itself was never actually changed. A BeforeSet trigger prevents the property

setter from updating the object. At the moment there is a discrepancy between the business object

property value and the corresponding widget control display property on screen 72.

72 We could set the DevForce BindingDescriptor.CancelEditOnError for the binding to LastName to true; this would

immediately restore the TextBox‟s display of the original value. The author dislikes that choice because it obscures what the

user was trying to do by replacing the user‟s data entry. She sees a warning about a problem that is no longer the problem.

Page 314: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

314 | P a g e

We can see reveal the discrepancy and cure it by scrolling off of the “Nancy” employee and then

returning to her. The TextBox refreshes with her current LastName property value which remains

“Davolio”.

EnableAllowFocusChange and AfterSet Triggers

The behavior is different for verifiers evaluated in response to AfterSet triggers.

If we had a LastNameRequiredVerifier and set its ExecutionModes to

InstanceAndOnAfterSetTriggers, the LastName property value would be empty, just as it appears in

the TextBox. A AfterSet trigger causes validation after the property has been set with the “proposed

value.”

We can confirm this by scrolling off of the “Nancy” employee and then returning to her. The TextBox

remains blank. The current LastName property value is empty.

However, we are no longer aware of the latent validation error. Our application does not validate the

Employee upon display … and that might be a user experience problem73.

At least it is not a data integrity problem – or doesn’t have to be. We must assume that the application

follows our advice and ensures that every entity must survive “instance verification” before it can be

saved. We further assume that the application has some mechanism to display errant entities and their

problems. Perhaps a simple MessageBox will do.

This Employee will not survive validation, will not be saved, and the user will be told why.

Questionable User Experience

This approach may be viable if little time can pass between data entry and instance verification.

Some applications attempt a save whenever the user moves off the current screen. The user will never

lose sight of the LastName error bullet and the save effort will reveal all latent problems with this

employee.

Many applications delay save and allow the user to move around among entities with pending changes.

That’s how our tutorial works. Users can make a change to “Nancy”, scroll to “Andrew” and make

changes to him, then scroll back to “Nancy” to continue her updates.

In this kind of workflow, the user may not remember that there is a problem with the “Nancy” object for

minutes or hours. When the application finally tells the user about this problem, the mental context is

long gone and the application will be perceived to be “unfriendly”.

73 We could write code to perform “instance validation” whenever the Employee changed. We could capture the VerifierResults

and display them as well as light up bullets next to each widget. The code is not hard to write but it‟s not utterly trivial either.

We‟ll describe an approach that achieves something of that effect using a different technique.

Page 315: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

315 | P a g e

There is another, potentially greater risk. The user may make a critical business decision base upon what

is visible on the screen. That data could be in error. The user won’t know it if she scrolled off and then

back on to the record.

If this risk is serious, the application must behave differently whenever the UI displays a new object – a

new Employee in our example.

Instance Verification Upon Display

One approach would be to perform instance verification whenever the currently displayed object is

changed.

Page 316: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

316 | P a g e

Validation Through Verification - Advanced

Validation Through Verification - Advanced ........................................................................... 316

Introduction ....................................................................................................................................................... 316

Verification Types Overview .............................................................................................................................. 317

Main Verification Classes .................................................................................................................................. 317

Verifiers ............................................................................................................................................................. 319

VerifierResult .................................................................................................................................................... 322

Triggers .............................................................................................................................................................. 324

VerifierEngine ................................................................................................................................................... 326

PropertyValueVerifiers ...................................................................................................................................... 328

Verification Deep Dive ........................................................................................................................................ 332

Verifiers ............................................................................................................................................................. 333

Verifier Result ................................................................................................................................................... 338

Triggers .............................................................................................................................................................. 341

VerifierEngine ................................................................................................................................................... 350

Invoking Verification........................................................................................................................................... 355

Instance Verification .......................................................................................................................................... 357

Trigger Verification: BeforeSet and AfterSet .................................................................................................... 358

Monitor Execution with the VerifierBatchInterceptor ....................................................................................... 362

Introduction The basics of DevForce’s validation facilities are covered in the separate topic document “Validation

Through Verification.” This document picks up where that one leaves off, delving deeper into the

Verification types and mechanics.

Page 317: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

317 | P a g e

Verification Types Overview

Most Verification classes are defined under the IdeaBlade.Validation namespace and deployed in a

single DevForce class library, IdeaBlade.Validation.dll.74 This section is a guide to the key

Verification constructs in that library.

Main Verification Classes

74 There are some legacy classes having to do with WinForm support that are defined in IdeaBlade.Verification.dll. You will

not need these if doing WPF or Silverlight applications.

Page 318: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

318 | P a g e

These are the main classes, the classes at the heart of the DevForce Verification paradigm.

Type Description

VerifierEngine A VerifierEngine maintains a list of Verifier instances in its

VerifierCollection and executes them at the appropriate times,

accumulating their VerifierResults in its VerifierResultCollection.

A Verifier should not run independently. Rather it should be evaluated

by a VerifierEngine and the caller should reap the results from the

engine’s Execute method when it finishes.

Verifier

VerifierCollection

A Verifier validates the state of an object.

This is the abstract base class for a family of verifiers described below.

VerifierResult

VerifierResultCollection

Verifier execution produces a VerifierResult object. This object, in

addition to signaling validation success or failure, contains detailed

information about the outcome and the context of the verifier’s

execution.

VerifierOptions A VerifiersOptions instance contains settings that determine many

aspects of a verifier’s behavior, such as when it it is run and how it

handles exceptions.

TriggerItem A TriggerItem identifies something like an “event”.

When the VerifierEngine executes in the context of this “event”, it

evaluates all verifiers attached to that TriggerItem.

A property is the most commonly encountered TriggerItem. The setting

of that property is the associated “event”. The engine looks for all

verifiers that are attached to that property “in the right way” and

executes them.

TriggerLink Triggered verifiers have one or more TriggerLinks, each of them

connecting the verified object to a TriggerItem.

The TriggerLink specifies both the TriggerItem and a “path” back to

the verified object.

In the case of a simple property it is the very short path from the

property to the instance as in the path from Employee.FirstName to

Employee.

Developers can write complex paths that “navigate” from a triggering

“event” on an object that is far removed from the object being validated.

For example, changing a customer’s credit limit property (the trigger)

could stimulate verification of all outstanding orders (the verified

objects) related to (the path to) that customer.

The Verification types are all interrelated, but we separate them by category for explanatory purposes.

Verifiers The Verifier class and its supporting types.

VerifierResult VerifierResult and related types.

Triggers TriggerItem, TriggerLink, and related types.

VerifierEngine The VerifierEngine and its supporting types

Page 319: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

319 | P a g e

PropertyValueVerifiers Describes the pre-defined verifiers for the most commonly

encountered validation cases.

Verifiers A Verifier validates the state of an object. A Verifier can only run after it is instantiated by a

VerifierEngine.

The Verifier class is the abstract base class for derived verifiers that are attuned to specific validation

tasks; we cover these derived classes in a separate section below.

The following table highlights significant members of the Verifier class.

Class Member Description

AddTrigger, AddTriggers

Adds a TriggerLink to the verifier. The verifier subsequently responds

to “events” associated with the TriggerItem in that TriggerLink.

ApplicableType The verifier validates objects of this type

DefaultSortOrder Static property that reveals the SortOrder given to new verifiers by

default.

Description The description of the verifier as displayed to the user.

ExecutionModes The situations in which the verifier should run. The value combines

flags from the VerifierExecutionModes enumeration.

GetDisplayName Returns a name for the member of a type as it should be displayed to the

user. “First Name” might be the display name for the FirstName

property of an Employee. The method is often used to construct the

Description from a message template.

InitializationOrder The integer position of this Verifier in its engine’s list.

IsApplicable Runs the method that indicates if this Verifier applies to a given instance

when run in a particular context. Returns a

VerifierApplicabilityresult.

OnErrorMode Set to one of the VerifierOnErrorMode enumerations (Stop, Continue)

that tells the engine whether it should stop or continue verifying if this

verifier produces an errant VerifierResult. Continue is the default.

RemoveTrigger, RemoveTriggers

Removes a TriggerLink from the verifier. The verifier no longer

responds to “events” associated with the TriggerItem in that

TriggerLink.

SortValue The engine executes verifiers in sorted order. The engine sorts verifiers

first by this SortValue and, when those are the same, by the order in

which the verifiers were added to the engine (InitializationOrder).

Thus, the developer can influence verifier processing order by setting

this SortValue.

TriggerLinks Returns the TriggerLinks attached to this verifier.

Page 320: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

320 | P a g e

Class Member Description

VerifierArgs Configuration data for the Verifier. Every Verifier is created with a

VerifierArgs instance, either explicitly or implicitly.

VerifierEngine The engine to which the verifier is attached.

VerifierOptions The set of VerifierOptions that will determine the behavior of this

verifier. Any options not set at this level will inherit their settings from

the parent VerifierEngine.

Verify For Internal Use. This method implements the verifier’s core

validation test and can be overridden by a derived class.

The method is public so that a VerifierEngine can call it at the

appropriate time. Developers cannot call it directly – it will throw an

exception.

The following is list of types that are closely related to Verifier. The list is (mostly) alphabetical to make it easier

to locate a type and for lack of more compelling organizational principle.

Type Description

ApplicabilityConstraint(Of T) Delegate that determines if a Verifier applies to a particular object

given the current TriggerContext and VerifierContext. It returns a

VerifierApplicability object. T is the type of the verified object.

The developer can invoke this constraint directly by calling

Verifier.IsApplicable.

DelegateVerifier(Of T) A harness for a custom verifier that validates an object of type T. The

developer can build almost any kind of verifier with an instance of this

class.

The developer writes the validation test inside a VerifierCondition

delegate (hence the name) and includes a reference to the delegate

method in the DelegateVerifier constructor. This verifier can be

configured with triggers in the same way as all other verifiers.

DelegatePropertyValueVerifier (Of T)

A harness for a custom property verifier that validates an object of

type T. Used to build a verifier triggered by a single property with the

purpose of evaluating the proposed or actual value of that property.

The developer implements the validation test inside a method that

conforms to the ValueVerifierCondition delegate and passes a

reference to the method in the DelegatePropertyValueVerifier

constructor.

The verifier behaves like any of the predefined PropertyValueVerifier

classes described below.

PropertyValueVerifierAttribute Each of the PropertyValueVerifiers can be specified declaratively by

decorating a property with the corresponding

PropertyValueVerifierAttribute.

TriggerContext An object passed to a Verifier when it is triggered by a TriggerItem.

The object provides the Verifier with information about what triggered

it.

Trigger classes are covered separately below.

Page 321: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

321 | P a g e

Type Description

ValueVerifierCondition(Of T) Delegate that determines if a value passes its verifier test. It returns a

VerifierResult. T is the type of the verified object.

The developer can supply such a delegate as an argument to the

constructor of a DelegatePropertyValueVerifier.

Verifier Abstract base class for a family of Verifiers. A Verifier validates the

state of an object and returns a VerifierResult containing detailed

information about the validation outcome and the context of the

verifier’s execution.

Verifier(Of T) Strongly typed abstract subclass of Verifier where T is the type of the

verified object.

VerifierApplicability The Verifier determines if it applies to an object it is validating based

on an ApplicabilityConstraint. That constraint method returns an

object of this type which contains both a VerifierApplicabilityCode

and an optional message.

VerifierApplicabilityCode An enumeration of result codes emerging from evaluation of an

ApplicabilityConstraint.

VerifierArgs These args carry configuration data for a Verifier. Every Verifier is

created with a VerifierArgs instance, either explicitly or implicitly;

every Verifier retains a reference to that instance.

This is also the base class for a family of VerifierArgs classes, each

strongly typed to fit closely with it corresponding Verifier class. The

ListVerifier has its ListVerifierArgs for example.

VerifierAttribute Abstract base class for a family of Attribute classes that enable

declaration of a Verifier by decoration with an attribute. For example,

we can declare that the FirstName property has a StringLengthVerifier

by adorning it with the StringLengthVerifierAttribute.

VerifierCollection A collection of Verifier instances. The collection implements many of

the features of List<Verifier> and, most importantly, many Find

overloads to facilitate extraction of Verifier subset collections.

VerifierCondition(Of T) Delegate that implements a validation test on an object of type T. This is

the beating heart of the developer’s custom DelegateVerifier.

VerifierContext The VerifierEngine executes a Verifier in a particular context and

makes this context available to the Verifier as it executes.

VerifierOnErrorMode The enumeration (Stop, Continue) that tells the engine whether it should

stop or continue verifying if this verifier produces an errant

VerifierResult.

The developer can set the Verifier.OnErrorMode to a value from this

enumeration.

VerifierException The exception thrown when the Verifier itself fails to execute properly,

i.e. when the Verifier throws an exception; that exception is included in

the InnerException.

Not to be confused with the VerifierResultException.

Page 322: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

322 | P a g e

Type Description

VerifierExecutionModes A flag enumeration (Disabled, Instance, OnAfterSetTriggers,

OnBeforeSetTriggers) that describes the situations in which a Verifier

can run.

The VerifierEngine, while executing in one of these situations, runs the

verifiers that have a matching ExecutionModes flag.

The developer can set a Verifier to run in multiple situations by setting

its ExecutionModes to a combination of these flags constructed by

“or”ing them together. The VerifierExecutionModes enumeration

exposes several of the most common combinations (e.g. All which

translates to Instance | OnAfterSetTriggers | OnBeforeSetTriggers

).

VerifierResult

Verifier execution produces a VerifierResult object75. This object, in addition to signaling validation

success or failure, contains detailed information about the outcome and the context of the verifier’s

execution.

75 We may say casually that a Verifier returns a VerifierResult but this is not strictly correct and might mislead the

developer into improper use of verifiers. While it is true that the Verifier.Verify method returns a VerifierResult,

that method executes only one part of the Verifier‟s validation logic and should not be called by developer code. The

Verifier should be executed by a VerifierEngine which stores the VerifierResult in its own results collection. The

developer retrieves those results from the engine.

Page 323: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

323 | P a g e

The following table highlights significant members of the VerifierResult class.

Class Member Description

Description Description of this result. The text is usually fashioned in a form suitable

for display when the result is “not Ok”.

IsOk True if the result has one of the “Ok” VerifierResultCodes.

ResultCode The VerifierResultCode for this result.

TargetInstance The object that was verified.

TriggerContext The TriggerContext in which the verifier was executed.

Verifier The Verifier whose execution produced this result.

VerifierContext The VerfifierContext in which the verifier was executed.

VerifierOptions The set of VerifierOptions that will determine the behavior of this

verifier. Any options not set at this level will inherit their settings from

the parent VerifierEngine.

The following are the important types that are most closely related to VerifierResult.

Type Description

VerifierResultCode Enumeration summarizing the result of verification in a single value.

While there are several codes, each is a flavor of a binary outcome:

success (Ok) or failure (Error).

The codes at this writing are: Error, ErrorInsufficientData, Ok,

OkNotApplicable, OkWarning.

VerifierResultCollection The VerifierEngine accumulates a collection of VerifierResult

instances which it returns as a VerifierResultCollection from its

Execute method. The collection implements most of the features of

Collection<VerifierResult> and, importantly, many Find overloads to

facilitate extraction of VerifierResult subset collections.

VerifierResultException The caller of the VerifierEngine may want to throw an exception if it

detects an errant VerifierResult. The VerifierResultException is a

strongly-typed exception for this purpose; it can report the initial errant

VerifierResult as well as a VerifierResultCollection of other results

that may be useful to a handler of the exception.

The Entity.BeforeSetValue and Entity.AfterSetValue methods are

examples of VerifierEngine callers that throw

VerificationResultExceptions.

The Verifier and the VerifierEngine do not themselves throw this

exception; they merely report errors by providing VerifierResults.

Do not confuse the VerificationResultException with the

VerificationException. A Verifier or VerifierEngine will throw a

VerificationException when the verifier fails to execute properly.

Improper verifier execution is not the same as an invalid object

condition.

Page 324: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

324 | P a g e

Type Description

VerifiersErrorsResource.resx A resource file of predefined error message templates.

This resource file contains the message templates for constructing

VerifierResult descriptions.

The developer can substitute a different .NET ResourceManager that

governs a wider set of message templates and resources files for

different locales; the developer’s main resource file must contain

definitions for all of the message names defined in the

VerifiersErrorsResource.resx.

The DevForce distribution includes this resource file as a starting place

for the developer’s own resource file (and satellite translation files).

Triggers Evaluation of a Verifier may be triggered by one or more “events”.

“Events” is in quotes because the mechanism, while it feels like an event, does not use the .NET event. The

exact mechanism is introduced here and covered more extensively elsewhere in this document.

Setting a property is likely the most commonly encountered trigger. Setting Employee.FirstName, for

example, could trigger evaluation of a Verifier that checked if the FirstName string value is present

and not longer than thirty characters.

The Verifier that checks the FirstName string length can be evaluated independently of any trigger. It

could be evaluated during validation of an Employee instance76. But we often want to verify the value

the moment the user enters the text. Accordingly, the developer attaches a trigger to that Verifier – a

trigger bound to the Employee.FirstName property.

TriggerItem

DevForce represents the triggering Employee.FirstName property as a TriggerItem. A TriggerItem is

little more than a .NET Type and the name of some member on that type. If a TriggerItem represents a

property, the member name is the property name.

TriggerLink

It will not always be enough just to know the TriggerItem. We may have to find our way back from the

trigger to the object being verified.

This is easy when the TriggerItem refers to a property of the object being verified. If the trigger is

Employee.FirstName and the Verifier targets the Employee object, it is obvious that “the way back”

from the property to Employee involves no effort at all: the triggering object and the verified object are

the same.

76 Evaluation in this situation is called “Instance Verification”.

Page 325: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

325 | P a g e

On the other hand, we may want to evaluate the verifier when a value changes on some different

object. For example, we may want to verify that an Order’s total price is still valid if the price of any of

its OrderDetail items goes up. The OrderDetail is not the same object as the Order we need to verify.

The TriggerLink provides the path from the OrderDetail whose price changed to its parent Order

which must be verified.

The TriggerLink holds both the end point (the TriggerItem for OrderDetail.Price) and the method

to navigate from the trigger object (OrderDetail) to the object to verify (Order). This method is called

the TriggerTargetNavigator.

We‟ll cover all of this in greater depth later; for now we look at the classes and other types involved in triggering

execution of a Verifier.

Type Description

TriggerContext An object passed to a Verifier when it is triggered by a TriggerItem.

The object provides the Verifier with information about what triggered

it.

TriggerItem A TriggerItem identifies something like an “event”. A

A TriggerItem is defined by the Type of the triggering object and the

name of a member on that type that does the triggering.

TriggerLink A TriggerLink specifies both the TriggerItem and a path back to the

verified object.

The path is implemented by a TriggerTargetNavigator method. That

method is null when the triggering object and the object to be verified

are the same as they are when we trigger a verifier for

Employee.FirstName when the user sets that property.

The navigator could be a.NET property path (e.g. a

PropertyDescriptor). It could also be a custom method capable of

bridging the two object types; see TriggerTargetNavigator.

TriggerTargetNavigator Delegate for “navigating” from a TriggerItem to the object being

verified. See TriggerLink.

TriggerTiming An enumeration available within the TriggerContext. It indicates when

a verifier was “triggered”. There are two choices: BeforeSet and

AfterSet.

Properties are the most common triggers so a TriggerTiming typically

indicates whether the verifier was evaluated before or after the property

was set.

Page 326: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

326 | P a g e

VerifierEngine Verifiers do not execute themselves77. They are executed by a VerifierEngine instance. Each engine

maintains a list of Verifier instances and evaluates them at the “appropriate” times based on a variety

of factors that include (but are not limited to) properties of the verifiers themselves.

77 You should not call the Verifier.Verify method directly even though it is public. That method performs some – but not

all ! – of the validation work and will throw an exception if called outside a VerifierEngine.

Page 327: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

327 | P a g e

The details of the engine are covered elsewhere in this chapter. Here are the types most relevant to understanding it.

Type Description

VerifierCollection The engine maintains a collection of Verifier instances, accessible via

one of the GetVerifier method overloads.

The collection implements most of the features of List<Verifier> and,

importantly, many Find overloads to facilitate extraction of Verifier

subset collections.

VerifierContext The VerifierEngine executes a Verifier in a particular context.

The engine creates an instance of a VerifierContext before every

validation run (a “batch”) and makes it available to the verifiers in that

run.

Each verifier can both see and modify the context. The developer can

activate a VerifierBatchInterceptor delegate method that can see and

modify the context.

The context includes a great deal of useful information including a

reference to the engine itself, the BatchId of the engine’s current

validation run, the VerifierResultCollection of results accumulated so

far, the currently executing Verifier, and a CustomContext object

supplied by the developer.

VerifierEngineCreatedEventArgs EventArgs provided to a VerifierEngine.VerifierEngineCreated event

handler. The VerifierEngine class raises this static event after creating

a new VerifierEngine instance. The developer can attach a handler to

consistently configure every new instance.

VerifierEngine. PropertyNameTranslator

Delegate method that takes a type and a string (presumed to be the

property name) and returns the string that will be injected into the

message produced by the verifier.

A verifier description or message should appear in the user’s preferred

language.

The message templates can be localized but they often have a

placeholder for the property name. The “{0}” in the message “{0} is

required” will be filled by a property name at runtime. This name

should be localized as well.

The VerifierEngine.PropertyNameToDisplayNameTranslator property

takes such a delegate.

VerifierEngine. VerifierBatchInterceptor

Delegate method called by the VerifierEngine after every verifier

evaluation in a batch and once more at the end of the batch.

VerifiersErrorsResource A resource file of predefined error message templates.

VerifierException The exception thrown when the Verifier itself fails to execute properly

within the engine, i.e. when the Verifier throws an exception; that

exception is included in the VerifierException.InnerException.

Not to be confused with the VerifierResultException.

VerifierExecutionModes A flag enumeration (Disabled, Instance, OnAfterSetTriggers,

OnBeforeSetTriggers) that describes the situations in which a Verifier

can run.

The VerifierEngine, while executing in one of these situations, runs the

verifiers that have a matching ExecutionModes flag.

Page 328: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

328 | P a g e

Type Description

VerifierOnErrorMode The developer can set the Verifier.OnErrorMode to a value from this

enumeration (Stop, Continue). The value tells the VerifierEngine

whether it should continue (the default) or stop verifying if this verifier

reports that its validation failed.

VerifierProviderAttribute

VerifierResult The result of executing a Verifier. It contains detailed information

about the outcome and the context of the verifier’s execution

VerifierResultCollection The VerifierEngine accumulates a collection of VerifierResult

instances which it returns as a VerifierResultCollection from its

Execute method. The collection implements most of the features of

Collection<VerifierResult> and, importantly, many Find overloads to

facilitate extraction of VerifierResult subset collections.

VerifiersChangedEventArgs Args of the VerifierEngine.VerifiersChanged event, raised when a

verifier is added to or removed from the engine or a trigger is added to

or removed from a verifier already held by the engine.

VerifiersChangedType Enumeration of the types of changes reported in VerifiersChangedEventArgs

PropertyValueVerifiers

The class diagram for Verifier and its derived classes as of this writing looks like this:

Page 329: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

329 | P a g e

Most of the verifier classes are PropertyValueVerifiers. A PropertyValueVerifier tests a property

value. Technically, it is a Verifier attached to single TriggerItem which is a property on the object

being validated.

The value to test may be the proposed property value (prior to the property set) or the current value

(after the property was set).

Many application validations are property validations and most of these resolve into some variation of

just a few kinds of verifier: required, range or length, and membership in a list.

Attribute Classes

Verifiers can be prescribed programmatically and added to the VerifierEngine at runtime. It is

sometimes convenient to prescribe them programmatically by adorning properties with attributes. The

DevForce includes a number of PropertyValueVerifierAttribute classes to facilitate this approach.

The DevForce Verification library covers many of these verifiers and attributes; of course you can easily

extend them or write your own.

Null property values

We have to check for null before we can test a property value. In many cases, null values are not

permitted. Rather than oblige the developer to specify both a RequiredValueVerifier and the verifier

of interest, all PropertyValueVerifiers include an IsRequired parameter; the base, abstract

PropertyValueVerifier evaluates IsRequired before handing the value on to the derived verifier

classes.

The outcome of the test is often arbitrary in the face of a null value; is a null BirthDate before or after

the minimum date in a range check? You should be sure you know how the verifier handles nulls.

The following table highlights significant members that are specific to the PropertyValueVerifier class.

Type Description

DisplayName The displayable name of this verifier; this is typically the display name

for the property it verifies. See also the Verifier.GetDisplayName

method.

GetPropertyValue Returns the value of this property as it currently is in the object being

verified. This value could be compared to the proposed value if the

verifier is executing in a “BeforeSet” context.

IsRequired Returns true if a property value is required (if it cannot be null).

PropertyDescriptor The .NET PropertyDescriptor for the property it verifies.

TypeVerifierArgs Returns the strongly type PropertyVerifierArgs that configure this

verifier.

Page 330: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

330 | P a g e

The following are types closely related to this class and its derived classes.

Type Description

DelegatePropertyValueVerifier (Of T)

The foundation of a custom property verifier. The developer implements

the validation test inside a ValueVerifierCondition delegate of his own

devising. It behaves otherwise like any of the predefined property

verifiers.

NamedRegexPattern A Regex expression for use with the RegexVerifier. You can use one of

the pre-named static patterns or create your own “named” Regex

pattern.

PropertyValueVerifier Verifiers that apply to a single property of an object. The DevForce pre-

defined PropertyValueVerifiers, as of this writing, are:

DateTimeRangeVerifier

DecimalRangeVerifier

DelegatePropertyValueVerifier(Of T)

DoubleRangeVerifier

Int32RangeVerifier

Int64RangeVerifier

ListVerifier

RangeVerifier

RegexVerifier

RequiredValueVerifier

StringLengthVerifier

PropertyValueVerifierAttribute Each of the PropertyValueVerifiers can be specified declaratively by

decorating a property with the corresponding

PropertyValueVerifierAttribute.

DateTimeRangeVerifierAttribute

DecimalRangeVerifierAttribute

DelegatePropertyValueVerifierAttribute

DoubleRangeVerifierAttribute

Int32RangeVerifierAttribute

Int64RangeVerifierAttribute

RangeVerifierAttribute

RegexVerifierAttribute

RequiredValueVerifierAttribute

StringLengthVerifierAttribute

RangeVerifier(Of T) A generic range Verifier where T is the type of value tested (not the

type of the verified object).

A range verifier accepts arguments specifying minimum and maximum

(either optional) and whether the range includes or excludes either end

point.

ValueVerifierCondition(Of T) Delegate that determines if a value passes its verifier test. It returns a

VerifierResult. T is the type of the verified object.

The developer can supply such a delegate as an argument to the

constructor of a DelegatePropertyValueVerifier.

Page 331: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

331 | P a g e

Adding Attributed Verifiers to Generated Properties

Previously you saw an example of the code generated by the Object Mapper for a string-valued

property, FirstName. The property definition was decorated with a StringLengthVerifier :

C#

[StringLengthVerifier(MaxValue=30, IsRequired=true)]

public String FirstName {

...

}

VB

<StringLengthVerifier(MaxValue:=30, IsRequired:=True)> _

Public ReadOnly Property FirstName() As String

...

End Property

To add an attributed verifier to a custom property defined in your developer partial class, you would

simply add the appropriate attribute – such as the StringLengthVerifier attribute shown above – to the

property definition.

Clearly you can’t do the same for properties defined in the designer code file generated by the DevForce

Object Mapper. That file, and the code in it, “belongs” to the Object Mapper, which reserves the write

to overwrite it whenever ordered to do so.

Nevertheless, you can still apply your own attributed verifiers to generated properties. You do this by

means of a “buddy” class that partners with your business class and contributes additional metadata to

it. In the example below, we’ve added such a buddy class to the developer partial class file for the

Customer type, Customer.cs. In the buddy class, we’ve decorated the static property CompanyName

with the StringLengthVerifier, assigning our own MaxValue, which is more restrictive than the one

generated for CompanyName in the designer code file.

Page 332: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

332 | P a g e

C#

...

using IbVal = IdeaBlade.Validation;

using DataAnnot = System.ComponentModel.DataAnnotations;

namespace DomainModel {

[DataAnnot.MetadataType(typeof(CustomerMetadata))] public partial class Customer : IdeaBlade.EntityModel.Entity {

...

}

/// <summary>

/// The buddy class for Customer

/// </summary>

public class CustomerMetadata {

/// <summary>

/// Override CompanyName to make it required.

/// </summary>

[IbVal.StringLengthVerifier(MaxValue = 10, IsRequired = true)] public static string CompanyName;

}

}

VB

...

Imports IbVal = IdeaBlade.Validation

Imports DataAnnot = System.ComponentModel.DataAnnotations

Namespace DomainModel

<DataAnnot.MetadataType(GetType(CustomerMetadata))> _ Partial Public Class Customer

Inherits IdeaBlade.EntityModel.Entity

...

End Class

''' <summary>

''' The buddy class for Customer

''' </summary>

Public Class CustomerMetadata

''' <summary>

''' Override CompanyName to make it required.

''' </summary>

<IbVal.StringLengthVerifier(MaxValue := 10, IsRequired := True)> _ Public Shared CompanyName As String

End Class

End Namespace

Important!! Note, in order that the Verification engine should be aware of the Customer type’s buddy

class, that we have decorated the Customer class with the MetadataType attribute from the

System.ComponentModel.DataAnnotations namespace. Don’t forget to do that: otherwise the verifiers

defined in your buddy class will not be enforced!

Verification Deep Dive

Now that we have toured the Verification types we are ready to look more closely at the major types.

Page 333: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

333 | P a g e

Verifiers We use an instance of the DevForce abstract Verifier class to implement a validation rule.

Verifier = Validation Rule

A verifier’s primary task is to render judgment on the validity of an object. It isn’t suppose to change the

object, just evaluate it and pronounce the object valid or invalid.

That’s a big job – too big for any one verifier instance78. So we create lots of verifiers each of which limits

itself to evaluating one aspect of an object such as the string length of a single property. Each verifier

produces a VerifierResult object which, at its most basic, (a) indicates success (Ok) or failure (Error)

and (b) provides a message (the VerifierResult.Description) for display to a user. An object is

“valid” if the accumulated results of individual verifiers are all “ok”.

The DevForce Verification library contains several predefined Verifier subclasses79 as well as several

higher level abstract classes that allow developers to construct their own verifiers.

Verifiers don’t execute on their own. They have to be evaluated by a VerifierEngine which means we

have to tell the engine about them by registering configured instances of some verifier class with the

engine.

While we can register verifier instances programmatically, it is often more convenient to let the

VerifierEngine discover them – a process we’ll get to when we consider the engine in detail. For now

we’ll talk about registration as if we always took an active hand in it.

Each verifier has an ApplicableType which is the type of object that the verifier can verify. Verifiers

with an ApplicableType of a .NET base type are presumed to be applicable to all subclasses of that

base type. The VerifierEngine ensures that verifiers registered for a base class are propagated

automatically the verifier collection of all derived types.

Imagine that you had an abstract class called Produce and a bunch of subclasses – Carrot, Apple,

Potato. When you attach a verifier to Produce.Name, that same verifier applies to Carrot.Name,

Apple.Name, and Potato.Name80.

Verifier Execution A verifier cannot be executed until it has been added to a VerifierEngine. An individual verifier

instance can be attached to only one VerifierEngine at a time.

78 While it is possible to write a single super verifier that does it all, it would be unwise to do so.

79 See the class diagram above.

80 It is the same verifier even if Potato.Name overrides Produce.Name. The developer can remove or replace the propagated

verifier for Potato.Name by manipulating the Potato verifiers after they have been built. Carrot.Name and Apple.Name

will be unaffected.

Page 334: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

334 | P a g e

Verifiers are executed in the order that they were added to the VerifierEngine. It is possible to modify

the order by setting the SortValue property on each verifier.

A VerifierEngine runs in one of three “Execution Modes” at a given time. How we call it determines the mode.

Instance Verification

BeforeSet Trigger Verification

AfterSet Trigger Verification

We cover these modes in detail in the “Invoking Verification” section. The point to note here is that the

engine will only evaluate the verifiers that are configured to run in a compatible execution mode. Thus,

if the engine is running in “BeforeSet Trigger” mode and the verifier’s ExecutionModes =

InstanceAndOnAfterSetTriggers, the verifier will not be evaluated; it will be evaluated when the

engine runs in either instance or AfterSet trigger mode.

When a VerifierEngine evaluates a verifier it calls two verifier methods: IsApplicable and Verify.

C#

public virtual VerifierApplicability IsApplicable( Object pItemToVerify, TriggerContext pTriggerContext, VerifierContext pVerifierContext); public abstract VerifierResult Verify( Object pItemToVerify, TriggerContext pTriggerContext, VerifierContext pVerifierContext);

Visual Basic

Public Overridable Function IsApplicable( _ ByVal pItemToVerify As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) _ As VerifierApplicability Public MustOverride Function Verify(ByVal pItemToVerify As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) _ As VerifierResult

Page 335: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

335 | P a g e

Observe that engine calls both methods with the same inputs

Parameter Description

pItemToVerify The object instance to verify. Its type will be the same as or a descendent

of the ApplicableType of the verifier.

pTriggerContext A TriggerContext object that describes how the verifier was triggered –

a topic covered elsewhere in this chapter.

Note that this value is null (Nothing in VB) when the verifier was not

triggered (i.e., during “instance verification”).

pVerifierContext

IsApplicable

The execution cost of some verifiers may be high. We don’t want to pay that cost if the verifier does not

apply in the present circumstances. The developer can specify an IsApplicable method to short-circuit

unnecessary verifier evaluation. For example, most validations are irrelevant if the object is marked for

delete. We might test for that in our IsApplicable method.

The VerifierEngine calls the verifier’s IsApplicable method first. The IsApplicable method returns

a VerifierApplicability object with a VerifierApplicabilityCode. If the code is Yes the engine

continues evaluating the verifier. If the code is anything else, the engine stops evaluating, prepares a

VerifierResult for this verifier, and moves on to the next verifier.

The VerifierResultCode of the prepared VerifierResult will be an “ok” code

(VerifierResultCode.OkNotApplicable) if the VerifierApplicabilityCode is No. It will be an

“error” code (VerifierResultCode.ErrorInsufficientData) if the VerifierApplicabilityCode is

InsufficientData.

An applicability test is rarely needed. Accordingly, the base IsApplicable implementation in the

abstract Verifier class simply returns VerifierApplicability.Yes.

VerifierContext The VerifierEngine provides both the IsApplicable and the Verify methods with a

VerifierContext defined as follows:

This context gives each Verifier information about its calling and executing environment including the

engine’s progress during this particular execution. The context values change over the course of the

verification. The VerifierEngine will change them. Each Verifier can change them too.

Initializing the VerifierContext

The initial VerifierContext is either a context created by the engine or a context provided by the code

that puts the engine to work.

Page 336: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

336 | P a g e

Such code calls one of the engine‟s Execute methods. There are a number of signatures, as we‟ll see later

in this chapter, and many of them take a VerifierContext.

If the caller provides the context, it will have instantiated the context with this constructor:

C#

VerifierContext(VerifierOnErrorMode pOnErrorMode, Object pCustomContext)

Visual Basic

New (ByVal pOnErrorMode As VerifierOnErrorMode, ByVal pCustomContext As Object)

The VerifierOnErrorMode is an enumeration with two values - Stop and Continue – meaning “Stop

verifying if you encounter an error” and “keep verifying until there are no more verifiers to evaluate”81.

The “CustomContext” can be any kind of object. It is a mechanism to enable the calling code to

communicate situational information to the verifiers that know how to interpret that information.

If the caller does not provide a VerifierContext, the VerifierEngine constructs one from its own

resources: the VerifierEngine.DefaultOnErrorMode and the

VerifierEngine.DefaultCustomContext. The application could set these defaults when it creates the

engine instance; it can revise them at will.

Features of the VerifierContext

Here is the interface of the VerifierContext

C#

public class VerifierContext { public Int64 BatchId { get; } public VerifierOnErrorMode OnErrorMode { get; set; } public Object CustomContext { get; set; } public VerifierResultCollection VerifierResults { get; } public object BatchContext { get; set; } public bool EndOfBatch { get; } public Verifier Verifier{ get; } public VerifierEngine VerifierEngine { get; } }

Visual Basic

Public Class VerifierContext Public ReadOnly Property BatchId() As Int64 public Property OnErrorMode() As VerifierOnErrorMode public Property CustomContext() As Object public ReadOnly Property VerifierResults() As VerifierResultCollection public Property BatchContext() As Object public ReadOnly Property EndOfBatch As Boolean public ReadOnly Property Verifier As Verifier

public ReadOnly Property VerifierEngine As VerifierEngine

81 An individual verifier can terminate the batch even if the OnErrorMode is Continue.

Page 337: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

337 | P a g e

End Class

Let’s walk through them quickly.

Calling the engine’s Execute method initiates a new verification “batch” that lasts for the duration of

the method’s execution. The engine assigns the batch a unique BatchId.

As stated earlier, OnErrorMode returns an enumeration with two values - Stop and Continue – meaning

“Stop verifying if you encounter an error” and “keep verifying until there are no more verifiers to

evaluate”. A verifier can change this value at any time.

We’ve already met the CustomContext containing an arbitrary object defined by the developer and

made available either when the engine was called or through its DefaultCustomContext property.

Observer that the object can be reset at any time during the batch.

The VerifierEngine adds each verifier’s VerifierResult to the VerifierResultCollection in the

context. Verifiers can see prior results and take action accordingly82.

The BatchContext is a means of accumulating and communicating execution state within the batch. It

starts null (Nothing in VB). Any verifier can change it, perhaps depositing useful information for

downstream verifiers.

The EndOfBatch starts false. The VerifierEngine will set it to true after it evaluates the last verifier

in the batch. This flag is intended for use by a VerifierEngine.BatchInterceptor, a delegate method

called by the engine after it evaluates each verifier – and once more at the end of the batch when it sets

this EndOfBatch flag to true. The interceptor could perform “batch cleanup” when it sees the flag set

true.

The VerifierEngine records the most recently evaluated verifier in the context’s Verifier property.

This property is aimed at the VerifierEngine.BatchInterceptor which may need to take some action

after the engine evaluates a particular verifier.

The VerifierEngine also registers itself in the context’s VerifierEngine property. Verifiers don’t need

this – they know to which engine they belong. The VerifierEngine.BatchInterceptor does not know

what engine is running; it can find out by looking at this context property.

Custom Verifiers The Verification library comes with many predefined verifiers that cover the majority of cases. Of course

you have to be able to create your own – and you can do so easily. Keep reading and you will see

examples. You can find these same examples, in context, in the Learning Unit on Verification that ships

with DevForce.

82 They can even manipulate the VerifierResultCollection itself; one hopes they are prudent in doing so.

Page 338: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

338 | P a g e

Verifier Result We expect a verifier to render a binary decision most of the time. It’s usually a pass / fail test.

Accordingly, every verifier returns a VerifierResult with an IsOk property. Either it is or it isn’t.

More nuanced information is also available but there is always a firm yes or no.

If the validation failed we probably want to display a message to the user83 explaining how it failed. The

VerifierResult.Description contains the message prepared by the Verifier – a message that may

have been translated into the local language and culture.

The VerifierResult.Description comes from the Verifier.Description by default. The phrase

“First Name cannot exceed 30 characters” serves well both as the description of the Verifier and the

message to the user when the entered text exceeds 30 characters.

Customizing the Description When this is not satisfactory, the developer can customize the message.

Sub-class the Verifier and override the Description property

In this example in which the author wants to drive home the point about keeping the birth date reasonable. The

DateTimeRangeVerifier would be fine if not for the message. So the author fills out the

DateTimeRangeVerifier and then overrides the Description property.

C#

/// <summary>Default Ctor,</summary>

/// <remarks>

/// BirthDate is not required,

/// must be on or after global min date (<see cref="M:MinBirthDate"/>),

/// and before today.

/// </remarks>

public BirthDateRangeVerifier() :

base(typeof(Employee), // Type of the object being verified

Employee.BirthDateEntityProperty.Name, // Property trigger

false,// Non-null value is not required

MinBirthDate, true, // starting min date (inclusive)

DateTime.Today, false) { } // ending max date (exclusive)

public override string Description {

// ToDo: Localize

get {

return "Must be born after " + MinBirthDate.Year.ToString() +

"; No time travellers allowed!";

}

}

}

VB

83 Or perhaps to a log file if we are validating outside of a user interface.

Page 339: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

339 | P a g e

Create a Delegate Verifier

One of the easiest ways to create a new verifier is to create an instance of one of the delegate verifiers

as we showed above. The description is one of the parameters in their constructors.

C#

public DelegateVerifier(String pDescription, VerifierCondition<T> pVerifierCondition) public DelegatePropertyValueVerifier(String pDescription, String pPropertyName, bool pIsRequired, ValueVerifierCondition<T> pVerifierCondition)

Visual Basic

' DelegateVerifier Constructor Public Sub New (ByVal pDescription As String, _ ByVal pVerifierCondition As VerifierCondition(Of T)) ' DelegatePropertyValueVerifier Constructor Public Sub New (ByVal pDescription As String, _ ByVal pPropertyName As String, _ ByVal pIsRequired As Boolean, ByVal pVerifierCondition _ As ValueVerifierCondition(Of T))

Sub-class the Verifier and override the Verify() method

The Verifier.Verify method returns the VerifierResult picked up by the VerifierEngine. This gives

you complete control over the VerifierResult.Description which you can construct dynamically.

This is Verify‟s signature84

.

C#

public abstract VerifierResult Verify(Object pItemToVerify, TriggerContext pTriggerContext, VerifierContext pVerifierContext);

Visual Basic

Public MustOverride Function Verify(ByVal pItemToVerify As Object,_ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) As VerifierResult

Localization and Internationalization There are two mechanisms for localizing the messages reported through the VerifierResult

1. Use resource files for the message templates

2. Translate the property name that is injected into the template.

For example, the basic message template, “,0- is required”, is ready to use as “PropertyRequired”

message. At runtime we plug “First name” or “Last name” or whatever into the slot reserved by “,0-”.

84 Although the method is public and it would seem that you can instantiate all of its parameters, you cannot call it yourself; you

will get an exception if you try. This is deliberate; DevForce can ensure proper verifier execution only within a

VerifierEngine.

Page 340: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

340 | P a g e

If the application will be used by non-English speakers, we’ll want to translate the template and we’ll

want to translate the property names.

Message Templates

DevForce ships with standard error and warning message templates. The developer can replace them

with a completely custom version.

The developer creates the .NET resource files for each language85. The only requirement is that at least

the default file has an entry for all of the DevForce template keys.

A copy of the DevForce verification resource file is available from IdeaBlade as a starting point for

customization.

Visual Studio generates a strongly-typed ResourceManager class to support these custom files. The

developer sets the ErrorsResourceManager property of each new VerifierEngine to this

ResourceManager as shown.

C#

VerifierEngine engine = new VerifierEngine(); engine.ErrorsResourceManager = myResourceManager;

Visual Basic

Dim engine As VerifierEngine = New VerifierEngine() engine.ErrorsResourceManager = myResourceManager

“Property Names”

Most common verifiers apply to a single property and inherit from the PropertyValueVerifier. Their

message templates have a slot for the property name and the verifier knows how to fill that slot with

the property name after it has been translated.

The key to the process is the PropertyNameTranslator. The VerifierEngine has a

PropertyNameToDisplayNameTranslator property that takes a PropertyNameTranslator delegate

defined as follows

C#

public delegate String PropertyNameTranslator( Type pType, String pPropertyName);

Visual Basic

Public Delegate Function PropertyNameTranslator( _ ByVal pType As Type, ByVal pPropertyName As String) As String

The expected implementation takes a type-and-string (e.g. Employee and “FirstName”) and turns it into

a translated string.

85 The .NET practices for localization are beyond the scope of this document.

Page 341: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

341 | P a g e

Note that type-and-string also defines a TriggerItem. As with TriggerItem, the string is typically the

name of a member of the target type … but it doesn‟t have to be.

With that background we are ready to proceed.

All predefined PropertyValueVerifier subclasses within the Verification library observe the following protocol

when preparing a “property name” for insertion into the template:

If the engine has a PropertyNameToDisplayNameTranslator, that method is used to translate the property

name.

If there is no translator, the verifier tries the value of PropertyValueVerifier.DisplayName.

If DisplayName is null, the verifier looks for a .NET DescriptionAttribute adorning the object property.

It there is no such attribute, the verifier uses the property name.

This same protocol can be used within a custom verifier, even one multiple slots for multiple property

names and values. The translator is not limited to translating property names.

Triggers Evaluation of a Verifier may be triggered by one or more “events”.

“Events” is in quotes because the mechanism, while it feels like an event, does not use the .NET event. The

exact mechanism is introduced here and covered more extensively elsewhere in this document.

Setting a property is the most commonly encountered trigger. Setting Employee.FirstName, for

example, could trigger evaluation of a Verifier that checked if the FirstName string value is present

and not longer than thirty characters.

The Verifier that checks the FirstName string length can be evaluated independently of any trigger. It

could be evaluated during validation of an Employee instance86.

But it is often a kindness to the user if we validate the first name text at the moment she enters it rather

than wait for the entire Employee object to be evaluated. Accordingly, the developer attaches a trigger

to that Verifier – a trigger bound to the Employee.FirstName property.

Property validation of this kind - a property Verifier with an attached property trigger - is extremely

popular. It is so popular that DevForce provides the PropertyValueVerifier87 and a host of derived

verifiers to make it easy to specify property validation.

One approach is to adorn a property with one of the attribute-based versions of the PropertyValueVerifier as

we do for the FirstName property in the following example.

C#

/// <summary>Gets or sets the FirstName.</summary> [StringLengthVerifier(MaxValue=30, IsRequired=true)]

86 Evaluation in this situation is called “Instance Verification”.

87 The PropertyValueVerifier and its kin are covered below.

Page 342: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

342 | P a g e

public virtual System.String FirstName { // …

Visual Basic

''' <summary>Gets or sets the FirstName.</summary> <StringLengthVerifier(MaxValue:=30, IsRequired:=True)> _ Public Overridable ReadOnly Property FirstName() As System.String Get '…

A VerifierEngine discovers the attribute and the FirstName property it adorns and then adds a

StringLengthVerifier, triggered by the FirstName property, to its list of verifiers.

Something similar happens when we add the Verifier programmatically to a list of verifiers that we later

add to a VerifierEngine.

C#

// Add FirstName StringLengthVerifier to a list of verifiers. verifiers.Add(new StringLengthVerifier( typeof(Employee),"FirstName", true, 1, 30));

Visual Basic

' Add FirstName StringLengthVerifier to a list of verifiers. verifiers.Add(New StringLengthVerifier( _ GetType(Employee), "FirstName", True, 1, 30))

Behind the scenes, DevForce constructs a Verifier that can validate the Employee.FirstName property

and arranges for that Verifier to be evaluated when someone tries to set the Employee.FirstName

property88.

That “arrangement” is the trigger.

Adding Triggers Explicitly The predefined PropertyValueVerifiers and their corresponding attribute versions each add a property

trigger to a property verifier implicitly (which is to say, “automatically”).

When you create your own verifiers, you may want to add one or more triggers yourself. These you

must add explicitly.

It is easy to do with the EntityPropertyDescriptors generated by the Object Mapper89

as we see in this

example:

hireDateVerifier.AddTrigger( EntityPropertyDescriptors.Employee.HireDate )

You can add them by string name too.

hireDateVerifier.AddTrigger(“HireDate”)

This isn’t type safe and it assumes that the trigger property is a property of the object to be verified as is

usually the case. You can specify the trigger type if you want to do so90.

88 It will also be evaluated when the program validates the Employee object (that is, during “Instance Verification”).

89 You can extend them to include your custom properties.

Page 343: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

343 | P a g e

C#

hireDateVerifier.AddTrigger(typeof(Employee), “HireDate”);

Visual Basic

hireDateVerifier.AddTrigger(GetType(Employee), “HireDate”)

You may go far with just this much understanding of triggers. On the other hand, you may find you need

to dig deeper and then you’ll want to know about TriggerItem and TriggerLink.

TriggerItem The TriggerItem represents the triggering Employee.FirstName property.

The Employee.FirstName property serves two roles in our example. It is both the value that is validated

by the verifier and it is the “thing” that can trigger the verifier. We have to distinguish between the two.

At the moment, we are interested in the property only in its second role – in its capacity as a trigger.

Imagine that the verifier didn‟t look at the first name. Imagine that it performed some other Employee

validation such as checking to see if the person is old enough to be an Employee. We could still trigger this

verifier every time the user touched the FirstName property. The FirstName property serves in the

second role, as trigger, even though it plays no role at all in the validation.

A TriggerItem is little more than a .NET Type and a string called the MemberName. The string is almost

always the name of some member on that type. If TriggerItem represents a property, the MemberName

is the property name.

While most TriggerItems are properties, it should be clear that we can represent almost any member of a

Type as a TriggerItem. We could trigger evaluation of a Verifier with a method as easily as a

property.

In fact, the MemberName could be an arbitrary string that is not an actual member of the type.

TriggerContext

In the course of evaluating a Verifier, the VerifierEngine calls methods on that verifier.

Remember, the VerifierEngine calls these methods. You do not.

These methods include:

C#

public VerifierApplicability IsApplicable( Object pItemToVerify, TriggerContext pTriggerContext, VerifierContext pVerifierContext); public VerifierResult Verify( Object pItemToVerify, TriggerContext pTriggerContext,

90 Or if you have to do so for reasons that will become clear below.

Page 344: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

344 | P a g e

VerifierContext pVerifierContext);

Visual Basic

Public Function Verify( _ ByVal pItemToVerify As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) As VerifierResult Public Function IsApplicable( _ ByVal pItemToVerify As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) As VerifierApplicability

Notice that the second parameter is a TriggerContext. The TriggerContext provides the verifier with

vital information about how the verifier was triggered.

The engine does not have to be triggered to evaluate the verifier. It could evaluate an entire instance

without prompting by a trigger91

.

The TriggerContext is null in this situation – a fact the verifier may use to establish that it was not

triggered.

The following table highlights the key elements of a TriggerContext:

Class Member Description

ProposedValue Typically a value entered by the user. The value is not yet committed; if

the trigger is a property, the property has not yet been set to this value.

The ProposedValue is meaningful only when the trigger’s Timing is

BeforeSet.

By convention, the verifier evaluates the proposed value. If the value is

invalid (per the verifier), the triggering property should discard the

proposed value and leave the current property value intact.

The Entity.BeforeSetValue observes this convention.

Timing One of the TriggerTiming enumerations (BeforeSet, AfterSet) that

indicate whether the validation occurs before the triggering property

performs its task (BeforeSet) or after it has already performed its task

(AfterSet).

The ProposedValue is meaningful only when the Timing is BeforeSet.

TriggerItem The TriggerItem that inspired the VerificationEngine to evaluate the

Verifier.

TriggerItemInstance The object that pulled the trigger. The Employee instance is the

TriggerItemInstance in an Employee.FirstName trigger.

TriggerLink We have neglected the TriggerLink to this point, conveniently confining our attention to the

TriggerItem.

91 This is called “instance verification”.

Page 345: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

345 | P a g e

As it happens, the TriggerItem alone is insufficient if we are to support a robust validation system. The

TriggerItem tells us what kind of object triggered a Verifier. Now we have to find a way back from the

object trigger to the object being verified.

This is easy when the TriggerItem refers to a property of the object being verified. If the trigger is

Employee.FirstName and the Verifier targets the Employee object, it is obvious that “the way back”

from the property to Employee involves no effort at all: the triggering object and the verified object are

one and the same.

We wouldn’t bother with such minutia unless we had grander plans – and we do. We would like to

trigger evaluation of a Verifier when something happens much farther away.

Let’s change our example from Employee to Order. Suppose the user increases the quantity of an item

on Order, a change that typically increases the total price of the order.

Imagine that there is a verifier on the Order that constrains the total allowed amount of any order to a

maximum amount, an amount calculated per a rule that factors the role of the user entering the data

and the Customer’s credit limit. This verifier sits on the Order class.

We could wait until we validated the entire order before evaluating this verifier. If the change broke the

limit, we’d tell the user. But it might be better to tell the user right away. It might be better if the change

to the OrderDetail.Quantity property triggered the Order verifier immediately.

OrderDetail.Quantity is not a property of Order. It is one hop away, on the navigation path from

OrderDetail to Order. In other words, to make this trigger work, the VerifierEngine must be able to

follow the path from the triggering change in Quantity to OrderDetail and from there to Order.

Enter the TriggerLink. The TriggerLink includes both the TriggerItem and a method that can

navigate from the triggering object to the object to verify, a method known as the

TriggerTargetNavigator. In our order example, the navigator could be the method that implements

the nested property path from OrderDetail to Order.

Verifiers and TriggerLinks

We observed earlier that specifying a StringLengthVerifier for FirstName, simultaneously specifies

the Employee.FirstName as its TriggerItem.

It turns out that we are actually attaching a TriggerLink to the Verifier; the Employee.FirstName is

the TriggerItem contained within that link whose other half is the navigator to Employee.

When we use any of the PropertyValueVerifiers, we implicitly create a verifier attached to a

TriggerLink that refers to the chosen property as its TriggerItem. The DevForce syntax hides the

hook-up to make creating the verifier easy.

Page 346: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

346 | P a g e

Easy things should be easy. But hard things should be possible – and a full appreciation of what is

actually happening can open our eyes to more complex scenarios.

Let’s take a look at some syntax for adding a TriggerLink to a Verifier explicitly. First, the simple

case:

C#

TriggerLink aLink = new TriggerLink( new TriggerItem(typeof(Employee), "FirstName"), // TriggerItem null, false); // Navigation aStringLengthVerifier.AddTrigger(aLink);

Visual Basic

Dim aLink As New TriggerLink( _ New TriggerItem(GetType(Employee), "FirstName"), _ Nothing, False) aStringLengthVerifier.AddTrigger(aLink)

The TriggerItem consists of a Type and a property name, just as we expect. The navigator is null

(Nothing in VB) because there is no navigation necessary from the object that triggers the verifier to the

object that is verified – they are the same object.

The third Boolean parameter is false because the link does not return a collection and therefore cannot

return “multiple targets”. The meaning of this mysterious option will become clear shortly.

We would never actually add a simple property trigger this way. There is no reason to specify the TriggerLink or

even the triggering object‟s type. There is no navigator and the type of the trigger is the same as the type of the

verifier. Instead we would write, in both C# and VB,

aStringLengthVerifier.AddTrigger("FirstName")

Now look at the second case involving Order and OrderDetail:

C#

TriggerLink aLink = new TriggerLink( new TriggerItem(typeof(OrderDetail), "Quantity"), // TriggerItem "Order", false); // Navigation orderTotalPriceVerifier.AddTrigger(aLink);

Visual Basic

Dim aLink As New TriggerLink( _ New TriggerItem(GetType(OrderDetail), "Quantity"), _ "Order", False) orderTotalPriceVerifier.AddTrigger(aLink)

Page 347: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

347 | P a g e

This time we have a navigator. The navigator is indicated by the Order property, a property of OrderDetail that

returns the Order instance to verify. Apparently DevForce can convert a nested property path into a

TriggerTargetNavigator.

How It Works

Here in schematic form is how Verifiers, TriggerItems, and TriggerLinks come together under the control of

a VerifierEngine when the triggering object and the verified object are different.

Something in the trigger property implementation tells the VerifierEngine to verify92

, supplying it with the

means to identify the TriggerItem.

The VerifierEngine finds a TriggerLink for that TriggerItem and also the Verifier to which that

TriggerLink is attached.

The VerifierEngine extracts the TriggerTargetNavigator and calls it, passing the trigger object as a

parameter. The trigger object is the OrderDetail in our example.

The navigator returns the object to verify (the Order).

The VerifierEngine confirms that the object to verify is of the correct type (i.e., it matches the

Verifier.ApplicableType).

The VerifierEngine executes the Verifier, passing the trigger information (a TriggerContext) as one of

the parameters.

Triggering Multiple Verifiers

We said that the “OrderTotalPrice” verifier consults the customer’s credit limit when determining if the

total price of the order is valid.

If the user changes the customer’s credit limit, she could render the order valid or invalid. Not just one

order either. She could change the validity of all of the customer’s outstanding orders.

We might want to draw attention to this by adding a Customer.CreditLimit trigger to the “OrderTotalPrice”

verifier. Here‟s some syntax:

C#

TriggerLink aLink = new TriggerLink( new TriggerItem(typeof(Customer), "CreditLimit"), // TriggerItem "Customer.Orders", // Navigation true); // true = returns multiple targets orderTotalPriceVerifier.AddTrigger(aLink);

Visual Basic

Dim aLink As New TriggerLink( _ New TriggerItem(GetType(Customer), "CreditLimit"), _ "Customer.Orders", _ True) orderTotalPriceVerifier.AddTrigger(aLink)

Note that this time the third argument of the TriggerLink constructor is True. We had to add an

additional argument to signal that this TriggerLink could return multiple objects to verify93. The

92 We‟ll investigate how to engage the VerifierEngine in just a few moments.

93 If we said False, the link would return a single target object – a collection of Order. The “OrderTotalPrice” verifier applies

to a single Order instance, not a collection. There is a type mismatch between the verifier and the (collection) object returned

Page 348: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

348 | P a g e

VerifierEngine will execute the “OrderTotalPrice” verifier for each of the customer orders. If there are

twenty customer orders, there will be twenty VerifierResults.

TriggerLinks and Performance

We typically don’t worry about how long it takes to set a property. Now that we’ve introduced triggers

that can provoke a series of verifications, we should pause and reflect.

The navigator in this last example invoked the Orders property of a Customer instance. That Customer

may have thousands of orders, none of them in the entity cache. Calling Customer.Orders in this

situation usually means a trip to the data store. The UI could stall noticeably while DevForce runs out to

the server to fetch the orders.

The developer must be aware of this possibility if she is going to write fancy triggers like this one. She

may want to confine it to entities in the cache or only retrieve the orders that are still open94.

The Customer.Orders property can’t be changed. Fortunately, there is an alternative.

TriggerTargetNavigator Delegate

In our previous TriggerLink examples we specified the navigator with a nested property path. We could have

used a TriggerTargetNavigator delegate, defined as follows.

public delegate Object TriggerTargetNavigator(Object pInstance); Public Delegate Function TriggerTargetNavigator(ByVal pInstance As Object) _ As Object

It‟s a simple method that takes one object – the triggering object – and returns another object – the object to verify95

.

Here is the same TriggerLink, rewritten to use a TriggerTargetNavigator delegate method called

“aCustomerOrdersNavigator”.

C#

TriggerLink aLink = new TriggerLink( new TriggerItem(typeof(Customer), "CreditLimit"), // TriggerItem aCustomerOrdersNavigator, // Navigation delegate true); // true = returns multiple targets orderTotalPriceVerifier.AddTrigger(aLink);

Visual Basic

Dim aLink As New TriggerLink( _ New TriggerItem(GetType(Customer), "CreditLimit"), _ aCustomerOrdersNavigator, _ True) orderTotalPriceVerifier.AddTrigger(aLink)

from the TriggerLink; the VerifierEngine will raise a VerifierException indicating that the verifier‟s execution

failed.

94 There is no point in verifying closed orders.

95 Remember that this object can be a collection of objects. The boolean TriggerLink.ReturnsMultipleTargets property

tells the VerifierEngine whether to verify the items in the collection individually (true) or as a single object (false).

Page 349: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

349 | P a g e

This method could use DevForce persistence operations to do the navigation but it doesn’t have to. It

can have any implementation that returns objects that match the verifier’s target object type, the value

of Verifier.ApplicableType.

Do not use an asynchronous delegate. Validation is not workflow. Validation is an inherently

synchronous operation and the VerifierEngine is not thread safe. The navigator must return an object to

verify; the application must pause until that object becomes available.

PropertyDescriptor Syntax

We have shown the TriggerItem in its “native form” as a .NET Type and a member name.

The PropertyDescriptor alternative may be easier to enter, easier to read, and is certainly more type-

safe because the developer does not have to code the member name as a string.

Here‟s how to add the simple property trigger in a single statement using the PropertyDescriptor notation in

either C# or Visual Basic:

aStringLengthVerifier.AddTrigger(Employee.PathFor(e=>e.FirstName))

Here‟s how to add the TriggerLink with PropertyDescriptor notation.

C#

TriggerItem item = new TriggerItem(typeof(Customer),

Customer.CreditLimtEntityProperty.Name);

orderTotalPriceVerifier.AddTrigger(

new TriggerLink(item, // TriggerItem

Customer.PathFor(c=>c.Orders), // Navigation

True)); // true = returns multiple targets

VB

Dim item As New TriggerItem(typeof(Customer),

Customer.CreditLimtEntityProperty.Name)

orderTotalPriceVerifier.AddTrigger(

new TriggerLink(item, „ TriggerItem

Customer.PathFor(c=>c.Orders), „ Navigation

True)) „ true = returns multiple targets

Non-Property Triggers We tend to discuss triggers as if they were always property triggers. They usually are. But they don’t

have to be.

It takes a TriggerItem to trigger verification. The TriggerItem consists of a Type and a String called

the MemberName. The MemberName could be any string. Usually it is a property name but it need not be. It

could be a method name. It could be a string with no intrinsic meaning at all.

Page 350: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

350 | P a g e

The VerifierEngine uses the “type-and-string” to find verifiers to evaluate. It is as if the engine had a

dictionary of TriggerItems, each leading to a TriggerLink and each link leading to a Verifier96. The

“reality” of the MemberName is irrelevant from this perspective.

Any block of code can trigger verification. All it has to do is call a VerifierEngine in a trigger-like way as

discussed in the section “Invoking Verification”.

The DevForce Object Mapper generates property setter code that calls a VerifierEngine in a trigger-

like way. You do the same when you write your own custom settable properties.

You could put the same call logic inside a method. For example, you might trigger Order verification

inside methods that add or remove OrderDetail items so that you can immediately test the effect of

adds and deletes on the total price of an order.

TriggerTiming (BeforeSet, AfterSet) is a convention that you should follow but can adapt to your

purpose. Your AddOrderDetail method could trigger verification in a BeforeSet manner before adding

the new item97. If validation fails, the method could discard the item before it did any harm.

VerifierEngine The VerifierEngine is the primary entry point for verification services.

An application may have any number of VerifierEngines although most will only need one.

Each VerifierEngine contains a list of verifiers and a set of methods that allow collections of these

verifiers to be evaluated sequentially against an instance of a .NET class.

The verified object could be a DevForce business object but it doesn‟t have to be. The object can be of any

concrete type.

Each verifier produces a VerifierResult. The engine accumulates these results in a

VerifierResultCollection as it proceeds and returns the entire collection as its own result.

Adding Verifiers to a VerifierEngine Verifiers can be added to a VerifierEngine in two ways:

The engine can discover them automatically by inspecting the .NET types for verifier attributes.

The developer can add them programmatically.

The application can combine these methods.

96 Actually, a TriggerItem could lead to multiple TriggerLinks and each of those links could be attached to multiple

Verifiers. A single TriggerItem can launch an avalanche of verifications.

97 You must supply a ProposedValue. It can be any kind of object such as the item to be added. It could be null.

Page 351: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

351 | P a g e

We got a taste of verifier discovery in the “Getting Started” started section. We’ll cover it in more depth

shortly.

Programmatic management of an engine’s verifiers is straightforward via the AddVerifier and

RemoveVerifier methods. Verifiers can be added or removed from a VerifierEngine at any time.

The engine raises a VerifiersChanged event when verifiers are added or removed. available on the

verifier engine and will inform any subscriber of the addition or removal of any verifier98.

Verifier Discovery The VerifierEngine always discovers verifiers in the types it is asked to verify99. When a

VerifierEngine attempts to verify an instance of a type it has not seen before, it probes the type

reflectively, looking for verifiers.

The probing strategy is as follows.

Start with the most senior base class in the type‟s inheritance chain.

Look for instances of the VerifierAttribute100 class on members of that base class. These define the

“attributed verifiers”.

Look for a static method decorated with the VerifierProviderAttribute101;

Such a method must take a single parameter of type object – this is the “VerifierProviderContext” – and it

must return an IEnumerable(Of Verifier).

The engine calls the VerifierProvider and adds the Verifier instances returned by that method to its list of

verifiers for the base type.

Find the next class in the type‟s inheritance chain and return to step #2.

Stop when have descended to the type that initiated the discovery process.

We have seen the attribute verifiers earlier.

A VerifierProvider might look like this:

C#

#region Verification

#region GetVerifiers Method

/// <summary>Get Verifiers.</summary>

/// <param name="pVerifierProviderContext">Context in which these Verifiers

are retrieved.</param>

/// <returns>The verifiers.</returns>

[VerifierProvider]

public static IEnumerable<Verifier> GetVerifiers(Object

pVerifierProviderContext) {

98 The event is also raised when triggers are added or removed from a verifier that has been registered in the engine.

99 Automatic discovery is not always a good thing, and developers can disable an engine‟s automatic discovery. An engine with

automatic discovery disabled can still perform discovery when asked to do so.

100 DevForce provides a number of common verifiers in attribute form all of which descend from VerifierAttribute. The

developer can add custom VerifierAttribute subclasses just as he can add custom Verifiers.

101 Actually, there can be more than one such method in the class and the VerifierEngine will call each one.

Page 352: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

352 | P a g e

List<Verifier> verifiers = new List<Verifier>();

verifiers.Add(GetHireDateRangeVerifier());

verifiers.Add(new BirthDateRangeVerifier());

verifiers.Add(GetBornBeforeHiredVerifier());

verifiers.Add(GetPhoneNumberVerifier(Employee.HomePhoneEntityProperty));

return verifiers;

}

#endregion

#region Hire Date Verifier

/// <summary>Get a GetHireDateRangeVerifier.</summary>

/// <remarks>

/// Demonstrates building a highly focused verifier

/// by encapsulation a standard verifier

/// and its configuration.

/// </remarks>

private static Verifier GetHireDateRangeVerifier() {

Verifier v = new DateTimeRangeVerifier(

typeof(Employee), // Type of the object being verified

Employee.HireDateEntityProperty.Name, // Property trigger

false, // Non-null value is not required

MinHireDate, true, // starting min date (inclusive)

MaxHireDate, false); // ending max date (exclusive)

return v;

}

private static DateTime MinHireDate {

get {

return new DateTime(1990, 1, 1);

}

}

private static DateTime MaxHireDate {

get {

return DateTime.Today.AddMonths(1);

}

}

#endregion

#region BirthDateRangeVerifier inner class

/// <summary>Get the minimum BirthDate allowed.</summary>

private static DateTime MinBirthDate { get { return new DateTime(1900, 1,

1); } }

/// <summary>BirthDate Range Verifier</summary>

/// <remarks>

/// Illustrates changing the error messaging for a particular property.

/// Have to subclass to take control of the messaging.

/// Here the message is statically known so we override

/// <see cref="M:Description"/>;

/// if it were dynamic or if

/// <see cref="T:DateTimeRangeVerifier"/> constructed the

/// message dynamically, we would have overridden

/// <see cref="M:VerifyValue"/> and manipulated the

/// message while creating the <see cref="T:VerifierResult"/>.

Page 353: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

353 | P a g e

/// </remarks>

private class BirthDateRangeVerifier : DateTimeRangeVerifier {

/// <summary>Default Ctor,</summary>

/// <remarks>

/// BirthDate is not required,

/// must be on or after global min date (<see cref="M:MinBirthDate"/>),

/// and before today.

/// </remarks>

public BirthDateRangeVerifier() : base(

typeof(Employee), // Type of the object being verified

Employee.BirthDateEntityProperty.Name, // Property trigger

false,// Non-null value is not required

MinBirthDate, true, // starting min date (inclusive)

DateTime.Today, false) { } // ending max date (exclusive)

public override string Description {

// ToDo: Localize

get {

return "Must be born after " + MinBirthDate.Year.ToString() +

"; No time travellers allowed!";

}

}

}

#endregion

#region Born Before Hired Verifier

/// <summary>Get a BornBeforeHiredVerifier.</summary>

/// <remarks>

/// Demonstrates comparing two property values

/// by creating an instance of a

/// <see cref="T:DelegateVerifier{TVerifiedObject}"/>.

/// </remarks>

private static Verifier GetBornBeforeHiredVerifier() {

// ToDo: localize description

string description = "Must be born before hired.";

DelegateVerifier<Employee> v =

new DelegateVerifier<Employee>(description,

BornBeforeHiredCondition);

v.AddTriggers(Employee.BirthDateEntityProperty.Name,

Employee.HireDateEntityProperty.Name);

v.ExecutionModes =

VerifierExecutionModes.InstanceAndOnAfterSetTriggers;

return v;

}

/// <summary>

/// The <see cref="T:VerifierDelegate{TVerifiedObject}"/>

/// for the <see cref="M:GetBornBeforeHiredVerifier"/>.

/// </summary>

private static VerifierResult BornBeforeHiredCondition(

Employee pEmp, TriggerContext pTriggerContext, VerifierContext

pVerifierContext) {

if (pTriggerContext != null &&

// We are not checking the proposed value because don't expect to call it

BeforeSet

pTriggerContext.Timing == TriggerTiming.BeforeSet) {

throw new VerifierException("BornBeforeHired verifier not implemented for

Page 354: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

354 | P a g e

BeforeSet");

}

return new VerifierResult(pEmp.BirthDate < pEmp.HireDate);

}

#endregion

#region Phone Number Verifier

/// <summary>Get a GetPhoneNumberVerifier.</summary>

/// <remarks>

/// Encapsulates a standard RegexVerifier, subclassed so the description

can be customized.

/// </remarks>

private static Verifier GetPhoneNumberVerifier(EntityProperty

pPhoneEntityProperty) {

return new PhoneNumberVerifier(

pPhoneEntityProperty.EntityType, // Type of object being verified

pPhoneEntityProperty.Name, // Trigger

false, // Non-null value is not required

NamedRegexPattern.USPhone); // Regex pattern to use

}

private class PhoneNumberVerifier : RegexVerifier {

public PhoneNumberVerifier(Type pApplicableType, string pPropertyName,

bool IsRequired, NamedRegexPattern pattern)

: base(

pApplicableType,

pPropertyName,

IsRequired,

pattern

) { }

public override string Description {

get {

return base.Description +

" including area code [e.g., (206)555-1212, 206-555-1212, or

206.555.1212].";

}

}

}

#endregion

#endregion

VB

VerifierProviderContext

Observe that a VerifierProvider method has an object parameter called the

“VerifierProviderContext”.

This is an arbitrary object, open to the developer’s imagination. The VerifierEngine will pass it along

to each provider.

Page 355: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

355 | P a g e

The engine acquires this context object in one of two ways:

From the VerifierEngine.DefaultVerifierProviderContext which the developer must have

initialized before the engine starts its discovery process.

As the second argument to VerifierEngine.DiscoverVerifiers(Type, Object). This is a method

that forces verifier discovery for the given type.

The VerifierProviderContext object could be anything. It could be a pre-calculated list of verifiers for

the type. It could include the VerifierEngine itself so that the VerifierProvider can inspect and

manipulate the other verifiers for this type.

The application must set the VerifierEngine‟s DefaultVerifierProviderContext or call its

DiscoverVerifiers method early.

The engine starts auto discovery as soon as it receives a request to verify an instance of a type. That

discovery could fail or populate the engine with the wrong verifiers if the developer doesn‟t make these

calls first.

Recommended Verifier Loading Approach

We recommend that most applications rely on automatic discover to build up a VerifierEngine’s list of

verifiers.

It is ok to add or remove verifiers from a VerifierEngine programmatically outside of the class being

verified but you should have a good reason for the extra and unexpected complexity.

Some business requirements call for configurable validation rules. Verifiers can be represented in

metadata, saved to storage, retrieved when the application starts, and plugged in to a VerifierEngine.

Configuring New VerifierEngines Consistently While most applications will have only one VerifierEngine, there are good use cases for having two or

more.

Wherever there are multiple engines there arises the need to ensure that they are all configured

consistently and appropriately. We don’t want a rogue programmer blithely instantiating new engines

that lack a DefaultVerifierProviderContext or are missing some other critical setting.

The application can attach a handler to the static event, VerifierEngineCreated, on the

VerifierEngine class. The event is raised whenever there is a newly created engine. The new engine is

passed in the VerifierEngineCreatedEventArgs so that the handler can configure it.

Invoking Verification

Verifiers do not execute themselves nor can they be executed on their own. They must belong to a

(single) VerifierEngine and rely on that engine to make them do their validation work.

Page 356: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

356 | P a g e

A VerifierEngine doesn’t verify on its own either. Something has to tell it to verify.

DevForce shouldn’t perform any operation unless it is asked to do so. Verification is a potentially costly

operation. Perhaps as important, DevForce would not know what to do when it was done verifying.

Only the application developer can know when to verify and what to do with the results.

DevForce does provide an easy way to automate trigger verification of the properties of business

objects. The developer simply launches the Object Mapper and turns Verification on102. The Object

Mapper generates “setter” code to call the VerifierEngine at the appropriate time.

It is still up to the developer to invoke verification at other key moments in the application such as:

Verification of entities just before they are saved.

Trigger verification of custom, settable properties of business objects.

Verification upon business object fetch or merge.

Trigger verification of non-business objects.

Fortunately, there are .NET events for all of the key business object moments and trigger verification of non-

business objects looks just like trigger verification of business objects.

In every case, the developer calls one of the VerifierEngine.Execute overloads. The public Execute methods

available at this time fall into three “Execution Modes”:

Instance Verification

BeforeSet Trigger Verification

AfterSet Trigger Verification

We’ll examine each mode in this following segments. We’ll learn how calling the VerifierEngine’s

Execute method determines whether it will perform instance, BeforeSet, or AfterSet verification.

Before we do, it is important to remember that we do not call Verifiers; the VerifierEngine does that.

When we tell it to execute in one of the three modes, it will iterate over its internal list of registered

verifiers, evaluating each verifier that is enabled for the current mode.

A Verifier will only be evaluated if its Verifier.ExecutionModes matches the current mode!

For example, if a verifier’s ExecutionModes = VerifierExecutionModes.Disabled, the verifier won’t

be evaluated at all, no matter how we call the VerifierEngine.

Keep this in mind as you review the scenarios below.

102 We saw how to do this in the “Getting Started” section.

Page 357: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

357 | P a g e

Instance Verification

The following are the VerifierEngine.Execute overloads for instance verification:

Execute Overload Description

Instance Verification

1 Execute(object pInstance) Validate an instance within the default VerifierContext 2

Execute(object pInstance, VerifierContext pVerifierContext)

Validate an instance within a particular VerifierContext

3 Execute(object pInstance, IEnumerable<Verifier> pVerifiers, VerifierContext pVerifierContext)

Validate an instance with just the given list of

Verifiers103

.

Validate within a particular VerifierContext.

The “Instance Verification” Execute overloads validate an entire instance. The VerifierEngine

finds the Verifiers for the instance type

keeps only those with the Instance flag set in their Verifier.ExecutionModes

sorts them in execution order104

and evaluates them sequentially.

VerifierContext

Every verifier receives a VerifierContext object during its evaluation. The simplest Execute, which

accepts only the object to verify, passes along a VerifierContext constructed by the VerifierEngine.

The other signatures take a custom VerifierContext argument which the engine modifies before

handing to the verifiers.

One of the signatures lets you specify which verifiers the VerifierEngine should evaluate. These

verifiers must be registered with the VerifierEngine and their Verifier.ApplicableType must match

the type of the verified object.

When and Where to Verify an Instance

The business requirements dictate when and where to verify an instance.

Many applications provide the ability to validate an entity at any time and then ensure that every entity

passes validation before it can be saved. Accordingly, this author recommends:

Prepare business objects for instance verification

Inject a BaseEntity in the Entity Data Model Designer

Write a VerifyInstance method in that BaseEntity

Verify instances in your handler of the EntityManager.Saving event

Make sure you have such a handler on every EntityManager

103 All of the verifiers must have been registered with this engine or else the Execute method returns an exception.

104 Verifiers are sorted by Verifier.SortValue ; ties are broken by the order in which they were loaded into the engine

(Verifier.InitializationOrder).

Page 358: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

358 | P a g e

Iterate through the entities to be saved, calling VerifyInstance on each one

Accumulate the VerifierResults from each call

Cancel the save if there are any VerifierResults.

Report these results to the user.

These are basic techniques taught in the code samples in the DevForce Learning Resources,

demonstrated in DevForce movies, and incorporated (albeit in enriched form) in DevForce reference

applications.

Here is a simplified example of a VerifyInstance method.

C#

/// <summary>Validate object for all instance Verifiers.</summary> protected virtual VerifierResultCollection VerifyInstance() { return this.VerifierEngine.Execute(this); }

Visual Basic

''' <summary>Validate object for all instance Verifiers.</summary> Protected Overridable Function VerifyInstance() As VerifierResultCollection Return Me.VerifierEngine.Execute(Me) End Function

Observe that each instance has access to a VerifierEngine; this is the VerifierEngine that belongs to

its EntityManager.

Trigger Verification: BeforeSet and AfterSet Should we validate a value before we set the property or after we set the property? There is no

universally correct answer to this question.

BeforeSet Triggers Some bad values should never enter the object. If the object property concerned the dosage level of a

drug, we’d want to prevent entry of an invalid value. Ten thousand milligrams of something could be

fatal. We have to block that at the moment of data entry. We don’t want the user to be able to move

until the problem is corrected. We certainly don’t want that dosage to appear in the business object

ever – not even in cache.

This is the right place for BeforeSet trigger verification. In BeforeSet verification, the VerifierEngine

receives a “proposed value” from the caller. The engine creates a TriggerContext with

TriggerContext.Timing set to TriggerTiming.BeforeSet. It embeds the proposed value in the

TriggerContext.ProposedValue. Then it makes calls on the verifier(s) linked to the trigger, passing in

this TriggerContext so that the verifier (a) knows how it was triggered and (b) the value it should test.

By convention, the code that asks for BeforeSet trigger verification should examine the

VerifierResultCollection returned from the engine before doing anything more with the proposed

value. If the results collection contains an errant result – if VerifierResultCollection.AreOk is false –

the code should discard the proposed value.

Page 359: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

359 | P a g e

The following are the VerifierEngine.Execute overloads for “BeforeSet” trigger verification:

BeforeSet Trigger Execute Signatures Description

1 Execute(object pTriggerItemInstance, String pMemberName, Object pProposedValue)

Perform “BeforeSet” validation of verifiers on all

objects that are linked to the pMemberName property of

the pTriggerItemInstance.

The property will be set to the pProposedValue unless

the validation fails.

Verifiers receive the default VerifierContext.

We say that the property caused a “BeforeSet trigger

validation” 2

Execute(object pTriggerItemInstance, String pMemberName, Object pProposedValue, VerifierContext pVerifierContext)

Perform “BeforeSet” validation of verifiers on all

objects that are linked to the pMemberName property of

the pTriggerItemInstance.

Verifiers receive the given VerifierContext. 3

Execute(object pTriggerItemInstance, PropertyDescriptor pDescriptor, Object pProposedValue)

Perform “BeforeSet” validation of verifiers on all

objects that are linked to the given PropertyDescriptor

which translates to a property of the

pTriggerItemInstance.

Verifiers receive the default VerifierContext. 4

Execute(object pTriggerItemInstance, PropertyDescriptor pDescriptor, Object pProposedValue, VerifierContext pVerifierContext)

Perform “BeforeSet” validation of verifiers on all

objects that are linked to the given PropertyDescriptor

which translates to a property of the

pTriggerItemInstance.

Verifiers receive the given VerifierContext. 5

Execute(object pTriggerItemInstance, TriggerItem pTriggerItem, Object pProposedValue)

Perform “BeforeSet” validation of verifiers on all

objects that are linked to the given TriggerItem.

Verifiers receive the default VerifierContext. 6

Execute(object pTriggerItemInstance, TriggerItem pTriggerItem, Object pProposedValue, VerifierContext pVerifierContext)

Perform “BeforeSet” validation of verifiers on all

objects that are linked to the given TriggerItem.

Verifiers receive the given VerifierContext.

Setting a BeforeSet Trigger The natural place to trigger a BeforeSet validation is inside the setter of the property, before writing the

incoming value into the object. The code should provide the incoming value as the “ProposedValue”

parameter. The code may include a VerifierContext if it will help the triggered Verifier do its job but

the context is optional and may be null.

The VerifierEngine provides the Verifier with a TriggerContext object that (a) alerts the Verifier to

the fact that it was triggered and (b) provides the contextual information it needs to do its evaluation,

including the proposed value in this BeforeSet case.

See the “TriggerContext” section for more information.

Page 360: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

360 | P a g e

If the verification fails – if any BeforeSet Verifier produces an errant VerifierResult – the property

must do something. The .NET framework development guidelines suggest that it should throw an

exception. There is a VerifierResultException105 for this purpose.

A VerifierResultException should not terminate the application.

DevForce handles the exception gracefully when it occurs during data binding; see the “Error! Reference

source not found.” section. The developer must handle a VerifierResultException thrown outside of

data binding.

Entity.BeforeSetValue The Object Mapper generates an Entity.BeforeSetValue method that adheres to this

recommendation precisely106. The method is virtual; developers can override it in a base entity class if

they want different behavior or if they want to augment it with other behavior such as error logging.

AfterSet Triggers “Life and death” properties are relatively rare. It is usually ok if the property value is invalid while the

user is working with the object. We want the user to know the value is invalid. We want to block every

attempt to save invalid data. But we can tolerate bad values for a while.

For example, the employee’s home city may be a required value. We may not be able to save the

employee record until we have a complete and valid home address. We want the application to tell us

about the omission in time to correct it.

On the other hand, it isn’t going to harm anything if it stays blank while the user is entering new

employee information. If the user mistakenly enters the wrong city, she should be able to clear it. She

may not know the name of the correct city; it is better to leave the city blank than to leave the incorrect

city in place. This is fine as long as we prevent the user from saving the address.

Summarizing the requirement:

Permit entry of an invalid value but advise the user of that fact.

Prevent saving of an object with an invalid value and tell the user about that.

The rule – manifested in the Verifier - is the same in both cases. How we validate and what we do

with the result depends upon the context.

We covered the second scenario - block the save – when we discussed “instance validation” above. We

want “AfterSet” triggered validation to handle the first scenario.

“AfterSet” means that the property has already been set with the incoming, invalid value from the user

by the time we validate. There is no “proposed value” to worry about. We still want to validate the (now

current) property value and tell the user if there is a problem.

105 Its constructor accepts a VerifierResultsCollection parameter that handlers can interpret and present intelligently.

106 The appendix discusses the implementation of BeforeSetValue in detail.

Page 361: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

361 | P a g e

The following are the VerifierEngine.Execute overloads for “AfterSet” trigger verification:

AfterSet Trigger Execute Signatures Description

1 Execute(object pTriggerItemInstance, String pMemberName)

Perform “AfterSet” validation of verifiers on all objects

that are linked to the pMemberName property of the

pTriggerItemInstance.

Verifiers receive the default VerifierContext.

We say that the property caused a “AfterSet trigger

validation” 2

Execute(object pTriggerItemInstance, String pMemberName, VerifierContext pVerifierContext)

Perform “AfterSet” validation of verifiers on all objects

that are linked to the pMemberName property of the

pTriggerItemInstance.

Verifiers receive the given VerifierContext. 3

Execute(object pTriggerItemInstance, PropertyDescriptor pPropertyDescriptor, VerifierContext pVerifierContext)

Perform “AfterSet” validation of verifiers on all objects

that are linked to the pMemberName property of the

pTriggerItemInstance.

Verifiers receive the given VerifierContext. 4

Execute(object pTriggerItemInstance, TriggerItem pTriggerItem, VerifierContext pVerifierContext)

Perform “AfterSet” validation of verifiers on all objects

that are linked to the given TriggerItem.

Verifiers receive the given VerifierContext.

As always we must tell the VerifierEngine to perform verification.

The VerifierEngine will give the triggered Verifier a TriggerContext just as it did for the BeforeSet

trigger but this time there will be no proposed value; the verifier may have to fish the value out of the

object.

That shouldn’t be hard. The Verifier typically knows the property it verifies and this property is usually

the same property that triggered verification. A “First Name” StringLengthVerifier that is triggered

by input of first name text will know how to examine the FirstName property of the Employee instance

it verifies.

DevForce removes the guess work if the Verifier inherits from PropertyValueVerifier (as

StringLengthVerifier does). Every subclass of PropertyValueVerifier has a virtual

VerifyValue method that receives both the instance to verify and the value to verify.

It is slightly trickier if the instance triggering the verifier is different from the object instance verified. We

encountered such a case when we considered a “TotalPriceVerifier” on Order that is triggered by a

change to the price of one of its OrderDetails.

Fortunately, the Order’s “TotalPriceVerifier” can use the TriggerContext.TriggerItem.MemberName

(“UnitPrice”) to dig the changed price value out of the TriggerContext.TriggerItemInstance (the

OrderDetail instance).

Relatively few verifiers involve such circuitous triggering. The vast majority of verifiers are

PropertyValueVerifiers whose triggering and verified instances are the same object.

Page 362: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

362 | P a g e

Which leaves us with the small problem of invoking the VerifierEngine at the right time. As this is a

AfterSet trigger, we should call the engine immediately after the line that pushes the incoming value

into the trigger object.

Entity.AfterSetValue That is what the Object Mapper does when it inscribes an Entity.AfterSetValue method into the

generated property code 107.

What happens if the verification fails? We invoked the verifier for a reason, presumably to alert the user

to a problem.

The AfterSetValue throws a VerifierResultException just as the BeforeSetValue does. DevForce

and .NET handle this just fine if the exception occurs within data binding. The developer must handle the

exception if it occurs anywhere else.

AfterSetValue is virtual so developers can override it in a base entity class if they want different

behavior. We’ll consider an alternative implementation in the “Error! Reference source not found.”

section.

Remember that you can delay telling the user about invalid input and rely upon instance verification to

catch it just before save. You won‟t need AfterSet triggers if you go this route.

The Role of the Object Mapper

As we just noted, the Object Mapper includes the Entity.BeforeSet and the Entity.AfterSet methods in the

code it generates for properties unless you specify otherwise. It also generates an Args parameter for those methods

that specifies whether verification should be invoked BeforeSet and AfterSet. By default, it is invoked in both

situations.

Writing Verified Custom Business Object Properties Developers often write custom business object properties. Such properties are usually ReadOnly, which

is to say, they have a getter but no setter. Trigger validation is a non-issue if there is no setter.

When the developer needs to write a settable property, her code probably should parallel the code

generated by DevForce.

Monitor Execution with the VerifierBatchInterceptor Some applications need to monitor the progress of a VerifierEngine’s execution and intervene at

certain points.

The VerifierEngine.BatchInterceptor is the way to do it. The engine calls the interceptor after

evaluating each Verifier giving all of the visibility and opportunity it needs.

107 The appendix discusses the implementation of AfterSetValue in detail.

Page 363: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

363 | P a g e

An interceptor is a method that conforms to the VerifierBatchInterceptor delegate signature:

C#

public delegate VerifierOnErrorMode VerifierBatchInterceptor( Object pInstance, TriggerContext pTriggerContext, VerifierContext pVerifierContext);

Visual Basic

Public Delegate Function VerifierBatchInterceptor( _ ByVal pInstance As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) As VerifierOnErrorMode

Because the interceptor’s parameters are the same as the parameters of the Verifier methods,

IsApplicable and Verify(), it has the same visibility into the verification process as they do.

The interceptor can

see the last verifier evaluated by looking at the VerifierContext.Verifier.

review and edit the accumulating VerifierResultCollection by looking at the

VerifierContext.VerifierResults.

terminate the current batch at any time by returning VerifierOnErrorMode.Stop.

post-process the VerifierResults when the batch is done – because the engine will call it one last time with

the VerifierContext.EndOfBatch flag set true.

The following example shows how one could use an interceptor to curb run-away validations. In this case, it

terminates the batch on the third error:

C#

… VerifierEngine engine = new VerifierEngine(); engine.BatchInterceptor = MyBatchInterceptor; … private VerifierOnErrorMode MyBatchInterceptor( Object pInstance, TriggerContext pTriggerContext, VerifierContext pVerifierContext) { if ( pVerifierContext.VerifierResults.Errors.Count > 2 ) { pVerifierContext.VerifierResults.Add( new VerifierResult(false,"More than 2 errors encountered")); return VerifierOnErrorMode.Stop; } else { return VerifierOnErrorMode.Continue; } }

Visual Basic

… Dim engine As New VerifierEngine() engine.BatchInterceptor = AddressOf MyBatchInterceptor … Private Function MyBatchInterceptor( _ ByVal pInstance As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) As VerifierOnErrorMode If pVerifierContext.VerifierResults.Errors.Count > 2 Then

Page 364: Dev Force 2010 Developers Guide

IdeaBlade DevForce Validation Through Verification

364 | P a g e

pVerifierContext.VerifierResults.Add( _ New VerifierResult(False,"More than 2 errors encountered")) Return VerifierOnErrorMode.Stop Else Return VerifierOnErrorMode.Continue End If End Function

Page 365: Dev Force 2010 Developers Guide

IdeaBlade DevForce Developers Guide DevForce Silverlight Apps

365 | P a g e

DevForce Silverlight Apps

Features described in the section are included with the DevForce Silverlight product.

DevForce Silverlight Apps ......................................................................................................... 365

Overview - What is DevForce Silverlight? ........................................................................................................ 365

Creating a DevForce Silverlight Application .................................................................................................... 366

Silverlight Deployment Steps ............................................................................................................................ 366

Questions and Answers...................................................................................................................................... 367

Troubleshooting ................................................................................................................................................. 368

Overview - What is DevForce Silverlight? DevForce Silverlight allows you to deliver line of business applications in the browser with the kind of

responsiveness users expect from a desktop application. Developed for Microsoft Silverlight, the

browser plug-in which powers rich application experiences, it allows you to leverage your existing

DevForce experience with new tools and techniques to build serious applications.

A few things to note about Silverlight, and thus about DevForce Silverlight:

Silverlight is inherently n-tier. The client application executes in a sandbox on the browser, and must

communicate with a service to retrieve and save data. The DevForce Silverlight Business Object Server

(BOS) provides that service, and allows you quickly to have a Silverlight application retrieving and saving

to a database, using the domain model and business objects you're already familiar with.

Silverlight is inherently asynchronous. To avoid blocking the browser, Silverlight requires that all service

communications be performed asynchronously. This can be a bit challenging at first, but DevForce

Silverlight provides an asynchronous API very similar to the standard synchronous API, plus additional

features to make asynchronous programming as easy as possible.

In DevForce Silverlight, you have the EntityManager to hold your client-side entity cache and

communicate with the BOS, just like you would in a standard DevForce application. The Domain Model

is actually shared between the two environments, and DevForce handles the movement of your

business objects between tiers. You use the standard EntityQuery syntax to build true LINQ queries,

which can be directed against a back-end data source or against the local DevForce cache. Your queries

run asynchronously against back-end data sources, or synchronously against the local cache.

Page 366: Dev Force 2010 Developers Guide

IdeaBlade DevForce Developers Guide DevForce Silverlight Apps

366 | P a g e

Key to it all is the shared domain model. The domain model used by the Silverlight application is the

same domain model used on the server, or in any .NET DevForce application: not an anemic object

model with an unfamiliar API. You can add business logic - via custom methods and properties,

DevForce property interceptors, and DevForce verification - to your shared domain model. You can also

choose to deploy logic which is applicable to the client-side or server-side only.

Creating a DevForce Silverlight Application You can use several different approaches to create a Silverlight application with DevForce:

1. Use the DevForce Silverlight Application template.

You can find this project template by choosing File - New Project' or 'File - Add - New Project' in

Visual Studio. The template is in the DevForce 2010 folder under both the Visual C# and Visual

Basic project types. Its use will result in the creation of both the Silverlight and web application

projects for your DevForce Silverlight application.108 From here you can work on UI and domain

model features, or reference already created projects.

You'll use the Visual Studio Entity Data Model designer to create the domain model in the web

application project. DevForce will generate the code for that model, and will then automatically

add it to the Silverlight project as linked code so that it will be compiled into that assembly as

well.

2. Use the standard Silverlight application template.

The standard Visual Studio template for a Silverlight application will create both the Silverlight

and web application (or web site) projects. If you want the web application to host your BOS,

you will need to do the following:

Add the EntityService.svc and EntityServer.svc files to the project;

Add all necessary IdeaBlade references; and

Modify the web.config to include the appropriate settings for the BOS.

You can find samples of the EntityService.svc, EntityServer.svc, and web.config files in the folder

LearningResources\110_Deployment\Snippets\IIS Files, installed by DevForce within the

DevForce 2010 installation folder (typically installed under C:\Program Files). As with option #1,

you will use the Visual Studio Entity Data Model (EDM) designer to create the domain model.

DevForce will generate an object model from the EDM and link that object model to the

Silverlight project.

Silverlight Deployment Steps Please see the Deployment topic document (in the Deployment section of the Learning Resources) for

detailed instructions and information about deploying Silverlight apps.

108 Note that the Silverlight assembly will ultimately be deployed client-side; the web assembly server-side.

Page 367: Dev Force 2010 Developers Guide

IdeaBlade DevForce Developers Guide DevForce Silverlight Apps

367 | P a g e

Questions and Answers

1. What is the "linked" Silverlight domain model?

In order to provide a single "shared" domain model which can be used between application tiers,

DevForce Silverlight creates two versions of the model - one compiled with .NET assemblies and one

compiled with Silverlight assemblies. These two versions actually reference the same code files, and

use the "linked" file feature of Visual Studio so that only a single copy of any file is required. The

IdeaBlade OM Designer Extension (see Tools / Extension Manager from the Visual Studio main menu

after installing DevForce) performs this linkage for you: it will generate the domain model files into

the .NET (web) project, and then create links to these files in the Silverlight project.

The result is that the domain model is available to both environments: a Customer class is the same

whether it’s defined in the client Silverlight application, or the server domain model assembly. One

additional requirement also ensures that the types in your domain model can be shared: both the

namespace and assembly names must be the same for the two assemblies holding the domain

model.

2. Where is the debug log?

A "client-side" debug log is not provided in DevForce Silverlight. A debug log is generated on the

BOS server, but it contains the usual server-side messages. You can implement a custom

ITraceLogger if you need Silverlight tracing messages logged.

3. Do I have to host the BOS from IIS? And must it be the same web site that's serving the Silverlight application?

You can still host the BOS from either the console (ServerConsole.exe) or Windows Service (ServerService.

exe) in DevForce Silverlight. You can also host the BOS from a different web site than the Silverlight

application. In both scenarios you need to ensure that a policy file is in place to avoid getting a cross-domain

access error. You'll find a sample clientaccesspolicy.xml file in the

LearningResources\110_Deployment\Snippets\Silverlight folder installed by DevForce, along with a readme

explaining how to deploy the file.

4. Can a single BOS support both Silverlight and .NET client applications at the same time?

Yes it can, providing that your license allows it.

5. How can I bind anonymously typed objects in my Silverlight application?

The DynamicTypeConverter converts anonymously-typed objects to dynamically-typed objects for binding in

Silverlight applications. Use the Convert(IEnumerable) method to convert one or more instances of an

anonymous type to corresponding instances of a DevForce dynamic type.

A DevForce "dynamic type" is a System.Type created dynamically at runtime. Generally the primary use for

this conversion is in Silverlight applications, which do not support data binding to anonymous types. Projection

queries are one common example in which return data will be anonymously-typed.

6. How can I customize the communications channel to the BOS? For example, I need to set higher timeout values

and add security.

The default configuration used by DevForce uses HTTP binding, binary encoding, and a

MaxReceivedMessageSize set to the maximum value (2G), with all other attributes defaulting. To

override the DevForce defaults you can add a ServiceReferences.ClientConfig file to your Silverlight

Page 368: Dev Force 2010 Developers Guide

IdeaBlade DevForce Developers Guide DevForce Silverlight Apps

368 | P a g e

application, or implement a custom ServiceProxyEvents class. More information on sub-classing

ServiceProxyEvents is available in the API documentation.

A sample ServiceReferences.ClientConfig is provided with the DevForce installation, in the

110_Deployment\Snippets\Silverlight folder. Unless you’re familiar with these files, it’s best to copy

the sample into your project and customize that. To use: include the file in the Silverlight

application project and mark it as “Content”. Remember that both the client and server

configurations must be compatible for communications to succeed, so you will likely need to modify

your web.config file also. The 110_Deployment\Snippets\Sample N-tier config files folder contains

samples showing different communications configurations.

Troubleshooting

1. You attempt to Connect to the BOS from the Silverlight client and receive the exception "An error occurred

while trying to make a request to URI 'http://localhost:9009/EntityService.svc'"

Connection errors can have many causes, but the first thing to check, especially in a new application using the

ASP.NET Development Server, is that the Silverlight application is actually "served" by the web application.

You can see this by looking at the address bar in the browser. If it doesn't start with "http://" then the

application is instead loading from the file system. Why is this a problem? Because, for security reasons, a

Silverlight application cannot make service requests unless served by a web server. In DevForce Silverlight this

means that the application cannot connect to, or make other requests of, the BOS; thus, data cannot be retrieved

from or saved to the back-end data source.

The problem is easily remedied by ensuring that the web application project is always the startup

project in your solution.

2. "*** License violation *** - 'Distributed BOS' not supported with the current license: StandardEF"

You must have a license for DevForce Silverlight in order to develop Silverlight applications with

DevForce. The Silverlight samples in the Learning Units were created with a license key authorized

for Silverlight, and you'll be able to run the samples as long as you don't regenerate the domain

model. Once you regenerate the model with your license key, the sample will stop working due if

your key does not authorize DevForce Silverlight.

3. I get the following exception when trying to fetch: "Unable to locate type: XX.YY"

This not-so-friendly message may be caused by a type name mismatch between your .NET and

Silverlight domain model assemblies. DevForce will seamlessly transmit entities between the

Silverlight and BOS tiers, but it does this using what is essentially a "shared" domain model.

DevForce expects to see entities having the same fully-qualified type name, for instance

"DomainModel.Customer, DomainModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",

in both the .NET and Silverlight assemblies holding the model.

This is why DevForce attempts to keep the assembly and namespace names in sync between the

two projects, since without this type name equality, entities cannot move between tiers. This

restriction will likely be removed in later releases of DevForce Silverlight. To fix the exception,

Page 369: Dev Force 2010 Developers Guide

IdeaBlade DevForce Developers Guide DevForce Silverlight Apps

369 | P a g e

ensure that the assembly and namespace names of the two projects containing the domain model

are identical.

4. Why aren't my breakpoints working?

This has nothing to do with DevForce, but we run into it from time to time. Double-check the Web

properties on the web application project, and ensure that both the ASP.NET and Silverlight

debuggers are checked.

5. Your application was running initially and then crashes after a few minutes with an exception message such as:

“Object reference not set to an instance of an object.. ---> System.NullReferenceException: Object reference

not set to an instance of an object”.

You may have encountered a problem that occurs when the IIS application pool has recycled. One of

the best ways to insure this does not happen is to create a new application pool that does not

recycle on a time limited basis and then assign your application to that pool.

6. Your application had been running and then crashes after you make a change to one or more of the files in the

application directory. The exception includes this message: “Could not load file or assembly

'App_Web_........”.

You may have encountered a problem that occurs when files in the application folder no longer

match the compiled version located in the “Temporary ASP.NET Files” folder. You can force a rebuild

of your application by deleting the “bin” folder and then replacing it with a copy, or by running the

“aspnet_compiler.exe” command with the “-c” switch. You can find the command by first browsing

to the folder “%SystemRoot%\Microsoft.NET\Framework\” and then opening the v2.0.xxxxx

subfolder (the numbers after v2.0 can vary) . Here is an example using the virtual directory name of

the application:

aspnet_compiler –v /MyApp -c

7. FIPS Compliance

If your Silverlight application will be served from a web server on which FIPS (Federal Information

Processing Standards) compliance is enforced, you will need to make the following changes to both

the web.config and startup pages.

In the web.config, you must set debug to false when FIPS is enabled. This is true even during

development: you cannot set debug to true with FIPS enabled!

XML

<system.web>

<!--

Set compilation debug="true" to insert debugging

symbols into the compiled page. Because this

affects performance, set this value to true only

during development.

-->

<compilation debug="false">

Page 370: Dev Force 2010 Developers Guide

IdeaBlade DevForce Developers Guide DevForce Silverlight Apps

370 | P a g e

If you use an .html page (ex: index.html) instead of an .aspx page to host the Silverlight application,

you will need to delete:

XML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

in order to be compatible with Firefox.

8. How to tell if the BOS is running.

You’ve received an error message from your client application stating, “The remote server returned

an error: Not Found”. This is a communications error which occurs when the WCF client application

is unable to complete a handshake with the server. There are, unfortunately, a myriad of reasons

why this might occur, but one of the first things to check is whether the service is actually running.

You can do this easily: open the web browser and point it to the URL being used by the client

application. For example, if the client app.config contains the following …

C#

<objectServer

remoteBaseURL="http://localhost"

serverPort="9009"

serviceName="EntityService.svc/sl" />

… or no app.config is present, then open the web browser to http://localhost:9009/EntityService.svc

. (Note that the final “/sl” is not included when browsing for the service description.) If the service is

running, you will see a “Service description” page generated by WCF. If, instead, you see a page

showing error information, then you know the service cannot be started and that your application

will be unable to run. Usually the error message on the page has helpful diagnostic information.

9. Known Issues (Silverlight-Only)

The Copy Local property on DevForce references in the web project must be set to true for the apps

to run properly. This setting is required to allow the DevForce WCF services (defined in the *.svc

files) to be compiled correctly. If not set, the services will not start, the client application will be

unable to connect to the server, and you will see an error message as follows:

The remote server returned an error: NotFound. If the service is

unavailable, then also make sure that the endpoint bindings match

between client and server.

Page 371: Dev Force 2010 Developers Guide

IdeaBlade DevForce Developers Guide DevForce Silverlight Apps

371 | P a g e

When you begin your Silverlight solution using the DevForce Silverlight Application project template,

several DevForce assemblies are added as references to the web project; and for all, CopyLocal is set

to true. However, if you manually add or modify references, you may see that the property is

initially set to false (which is the Visual Studio default). Always check this property when you see

the above error.

Page 372: Dev Force 2010 Developers Guide

IdeaBlade DevForce Web Applications

372 | P a g e

Web Applications

Web Applications........................................................................................................................ 372

State of the Release Candidate Documentation ................................................................................................. 372

Introduction ....................................................................................................................................................... 372

The DevForce ASPDataSource Component ...................................................................................................... 372

Using the ASPDataSource in Development ...................................................................................................... 373

Overridable Methods for Select, Update, Insert, and Delete ............................................................................. 373

The EntityAdapterManager Class ...................................................................................................................... 374

The Configure Data Source Wizard ................................................................................................................... 375

Parameter Collection Editor .............................................................................................................................. 375

Retrieving Schema Information ......................................................................................................................... 376

Third Party Support ........................................................................................................................................... 376

State of the Release Candidate Documentation

We are working hard to update all of our documentation from DevForce 2009 to DevForce 2010, .NET 4,

and Silverlight 4. During this conversion, you may find some sections that are out of date, but you should

be able to get many of the examples to work, with small modifications, by checking against the API

Documentation for the current method signatures.

Introduction DevForce provides object mapping and a persistence framework that are as useful in web applications

as in WinForm and WPF applications. For a web application, you deploy DevForce server-side.

Communication between the client and server tiers occurs between the user’s browser and IIS; server-

side, requests for data are handed off to a two-tier DevForce application which communicates with the

back end data sources.

The DevForce ASPDataSource Component The datasource controls provided with ASPNET, notably the Object DataSource, are not designed to

work with objects that contain business logic, making them unsatisfactory for work with IdeaBlade

Page 373: Dev Force 2010 Developers Guide

IdeaBlade DevForce Web Applications

373 | P a g e

business objects. By contrast, the DevForce ASPDataSource control is an ASP.NET data source control

designed to work with rich business objects.

The ASPDataSource control is used at both run time and design time. Data binding occurs at run time.

At design time, functionality has been provided to allow Visual Studio 2005 developers to graphically

create web pages using common controls like the .NET DetailsView and GridView and third-party

controls like the Developer ExpressASPxGridView and ASPxPivotGrid, and the Infragistics

UltraWebGrid.

Using the ASPDataSource in Development The UI developer drags an ASPDataSource control onto a Web Form and interacts with it. In Visual

Studio, the ASPDataSourceDesigner exposes a Configure Data Source Wizard to permit the developer

to configure an instance of the ASPDataSource. When a control such as a .NET GridView bound to an

ASPDataSource control requests schema information about its data source, the schema information is

created and returned. This allows information to be returned not only about the simple properties

backed by table columns, but also about custom computed properties.

At runtime, the Web Form interacts with the ASPDataSource to perform the actual databinding. This

work is handled by the ASPDataSourceView, an instance of which is managed by the ASPDataSource

control.

Overridable Methods for Select, Update, Insert, and Delete Every ASP.NET DataSource control, whether provided in Visual Studio (e.g., the ObjectDataSource) or by

a third party vendor, allows the developer to specify methods and parameters for Select, Update, Insert,

and Delete methods. In many implementations, this is done by having the user select from a dropdown

list supplied by a wizard (as provided by the .NET ObjectDataSource) or by having the developer type in

the method name into a property sheet for the DataSource control (as done with the previous

DevForceDataSource). The required parameters for the CRUD method were specified in the code and

were collected at runtime using a ParameterCollection Editor.

We found that typing in the CRUD method name and then making sure that the parameters specified in

the code matched the parameters collected by the Parameter Colllection Editor was a process that was

both error-prone and difficult to debug. For the ASPDataSource, we decided to take a different

approach. We have declared four abstract methods for Select, Update, Insert, and Delete which must

be overridden by the developer to be used. By requiring the developer to use overridable methods,

many incorrectly written methods produce compilation errors rather than runtime errors, and

mysterious reflection errors are avoided.

Here are the signatures for the four abstract methods:

Page 374: Dev Force 2010 Developers Guide

IdeaBlade DevForce Web Applications

374 | P a g e

C#

public virtual IEnumerable SelectEntities(IOrderedDictionary

parameters, DataSourceSelectArguments pSelectArgs)

public virtual int InsertEntity(IOrderedDictionary parameters,

IDictionary newvalues)

public virtual int UpdateEntity(IOrderedDictionary parameters,

IDictionary keys ,IDictionary values, IDictionary oldvalues)

public virtual int DeleteEntities(IOrderedDictionary parameters,

IDictionary keys, IDictionary oldvalues)

The EntityAdapterManager Class The code for each instance of an ASPDataSource control is contained in an EntityAdapterManager

class. In the Instructional Unit for this feature, there are three ASPDataSource controls:

mEmployeeSelectorAdapterManager, a DropDown control,

mEmployeeEditorAdapterManager, a DetailsView control; and

mOrderAdapterManager, a GridView control. Every instance of an EntityAdapterManager has a read-only EntityTypeName (e.g, Employee or Order)

and an AdapterManagerAssemblyName (e.g., Web.Model, Version=1.0.0.0, Culture=neutral,

PublicKeyToken=null)

Page 375: Dev Force 2010 Developers Guide

IdeaBlade DevForce Web Applications

375 | P a g e

The Configure Data Source Wizard To prevent errors caused by mistyping the name of the EntityAdapterManager class, the developer is

encouraged to use a wizard. The ASPDataSource control has a wizard that provides a dropdown giving

the developer the choice of which EntityAdapterManager to use.

After the developer has selected an EntityAdapterManager, the EntityTypeName and

EntityAssemblyName properties are computed.

Parameter Collection Editor The ASPDataSource control uses a Parameter Collection Editor to configure the parameters for each

CRUD method. For example, in the Instructional Unit, the EmployeeEditorAdapterManager needs the

ID of the currently selected employee in the DropDown list at the top of the Web Form to be able to

perform its SelectEntities method.

Page 376: Dev Force 2010 Developers Guide

IdeaBlade DevForce Web Applications

376 | P a g e

Retrieving Schema Information The ASPDataSource supplies schema information to the web control to which it is attached. A

DetailsView control, for example, needs to know the name, datatype, and other property information

about the fields of the Entity that it is editing. A GridView needs to know the same kind of information

for the columns in its GridView. To signal its ability to supply this information, an ASPDataSource

control communicates its CanRefresh property. Then, the WebControl calls into the ASPDataSource

control to invoke the GetFields() function to get this schema information. Once the Web Control has

this information, it can display this information to the UI developer.

Third Party Support The ASPDataSource control works well with a variety of .NET Web Controls such as the DropDownList,

DetailsView, and GridView; it works equally well with well-known third-party web controls such as the

ASPxGridView and ASPxPivotGrid from Developer Express, and the UltraWebGrid from Infragistics. We

suspect that the ASPDataSource will work with many other third-party controls that we have not

specifically tested.

Page 377: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

377 | P a g e

Business Object Server

Business Object Server 377

Business Object Server Architecture ................................................................................................................. 377

EntityService Startup and Shutdown ................................................................................................................. 381

EntityServer Startup and Shutdown ................................................................................................................... 381

Remote Service Method Call (RSMC) Methods ............................................................................................... 382

Push Notification ............................................................................................................................................... 384

BOS Hosting Details ............................................................................................................................................ 385

The DevForce Client ......................................................................................................................................... 389

Vista Setup ........................................................................................................................................................... 391

Vista setup requirements for the ServerConsole or ServerService .................................................................... 391

Vista setup requirements for IIS ........................................................................................................................ 391

Troubleshooting ................................................................................................................................................... 392

Worked in 2-Tier, Strange Errors in n-Tier ....................................................................................................... 392

Business Object Server Architecture The Business Object Server (BOS) is a deployable binary package that exposes a singleton instance of the

DevForce EntityService class. There are three ways to deploy the BOS, each of which supports a

different deployment scenario:

BOS Deployment DevForce Assembly Executable

Console Application ServerConsole.exe

Windows Service ServerService.exe

IIS Server - not applicable -

An IIS deployment doesn‟t need its own executable. The EntityService is identified to IIS via a WCF

service file, for example:

EntityService.svc contents:

Page 378: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

378 | P a g e

<%@ServiceHost language="C#"

Service="IdeaBlade.EntityModel.Server.RemoteEntityService"

Factory="IdeaBlade.EntityModel.Server.EntityServiceHostFactory"

%>

The EntityService class itself is part of the IdeaBlade.EntityModel.Server.dll which must be

deployed to the executing directory of the physical server.

Each of the three BOS deployments “wakes up” in a different way:

BOS Deployment Wake up

Console Application When ServerConsole.exe is launched

Windows Service When the ServerService.exe Windows Service starts.

IIS Server When a EntityManager asks for service.

The singleton EntityService instance creates and manages one or more EntityServer instances. A

EntityService creates its first EntityServer when a EntityManager asks for BOS services.

One is plenty for many applications. But some applications will need more; to understand why, we must

understand the nature and purpose of “Datasource Extensions”.

Datasource Extensions Every EntityManager is associated with a “Datasource Extension” (called the “extension” for short). The

“Extension” marks a collection of one or more data sources, each the repository of a set of business

object classes mapped to objects in that data source.

Should your application access multiple data sources as shown in the above graph, your DevForce-

assisted domain model would be a single, unified business model integrating entities from each of these

sources. .

Deployment Extensions

Even if your application uses only a single database, there are other reasons you may need or want to

use multiple extensions. Many IT shops follow a rigorous deployment regime in which each new version

of the application progresses through a gauntlet of “environments” such as development, test, stage,

Page 379: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

379 | P a g e

and production. Indeed, your application may use multiple data sources while your deployment process

mandates a series of development environments, each of which requires a copy of the complete set of

such data sources.

In the illustration below, the “Test” environment has three data sources that parallel the

“Development”sources; they have the same schemas, but are different instances of the data sources,

and so have different “connection strings” (or Web Service equivalents) and potentially different data.

DevForce supports this regime through named extensions. The “Test” extension identifies its data

sources just as the “Development” extension has its sources. A full-blown diagram might look like so:

Tenant Extensions

Extensions are also a good way to segment data sources by client in a “multi-tenant application”. Multi-

tenant applications are typical of Application Service Provider (ASP) scenarios in which each customer’s

data are managed in isolated datasources.

When the user logs in, the application identifies the user’s parent customer and knows which set of data

sources is appropriate for that user. The application can then instantiate an EntityManager that draws

upon just those data sources.

The “Datasoure Extension” is the ideal representation for a customer-specific data source set as in this depiction of

a three-tenant scenario with customers “A”, “B”, and “C”:

Extensions and EntityServers Let’s stick with the multi-tenant, ASP scenario for awhile.

When the application client determines the customer, it creates an EntityManager dedicated to the

data sources applicable to that customer by including the customer’s “Datasource Extension” name in

the constructor.

C#

msManager = new EntityManager(true, "A"); // Connect to customer "A"

Now the client application tries to login or fetch entities with this EntityManager. The EntityManager

contacts the EntityService. The EntityService checks among its EntityServers for one that is

associated with extension “A”. It doesn’t find one so it creates a new EntityServer instance for

extension “A” and adds it to its collection. This EntityServer now serves every EntityManager

presenting the “A” extension.

Page 380: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

380 | P a g e

When the EntityService encounters EntityManagers with unknown extensions – “B” and “C” for example –,

it creates more EntityServers. The three-tenant scenario could look like this:

Review

The components intrinsic to and orbiting the Business Object Server are summarized in the following table:

BOS-Related Components Purpose and Function

Business Object Server A deployable binary package that exposes a singleton instance of the

DevForce EntityService class. Shipped in three flavors: console

application, Windows Service, and IIS.

EntityService DevForce type, a singleton instance of which handles the DevForce

“Business Object Server” functions on a physical middle tier. It

creates and manages one or more EntityServers, each identified by its

“Datasource Extension”. The EntityService routes EntityManager

requests to the EntityServer whose extension matches the

EntityManager’s extension.

EntityServer Performs server functions pertaining to a set of data sources

collectively identified by a “Datasource Extension.”

EntityManager The client-side manager of business objects. The EntityManager

makes requests for entities, authentication, and other services to the

EntityService, which routes those requests to the EntityServer whose

“Datasource Extension” matches the requesting EntityManager’s

extension.

EntityServiceApplication DevForce class that can be inherited and customized to perform

startup and shutdown behaviors for the EntityService. (Detail below.)

Page 381: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

381 | P a g e

EntityService Startup and Shutdown Our application may need to do some initial processing when the singleton EntityService starts up. It

might need to launch auxiliary server-side processes for example. Perhaps it should alter the in-memory

copy of the IdeaBlade Configuration file before creating its first EntityServer.

Perhaps our application should run server-side clean-up code when it shuts down. For example, it might

shut down the auxiliary services it started or send an email alert reporting that the service is coming

down.

Out of the box, the Business Object Server wakes up and goes straight to work. We need to do a little

programming if we want special startup and shutdown behaviors. We will create and register a sub-class

of the DevForce EntityServiceApplication class to do our pre- and post-processing.

One of the first steps for a new EntityService singleton is to acquire the singleton instance of a class that

derives from EntityServiceApplication. Such a class has two methods of obvious purpose:

public virtual void OnServiceStartup(object sender, ServiceStartupEventArgs e) public virtual void OnServiceShutdown(object sender, EventArgs e)

The EntityService looks for a EntityServiceApplication class in an assembly named in one of the

global probe assembly names listed in the IdeaBlade Configuration File.

It asks for a singleton instance of the first such class it discovers. If it can’t find such a class, it obtains the

singleton instance from the base EntityServiceApplication class in the DevForce EntityModel

library.

The EntityService then calls the EntityServiceApplication.OnServiceStartup method. When the

EntityService terminates, its finalizer calls OnServiceShutdown.

Invoking OnServiceShutdown inside the EntityService finalizer all but guarantees that the method

will be called, even if the EntityService dies by exception. Make sure that your implementation

“cannot fail” and does not raise an exception of its own – a no-no inside finalizers.

Adding custom pre- and post- behaviors

There are two steps to adding custom behavior:

1. Write a subclass of EntityServiceApplication that overrides these methods.

The overrides should call the base EntityServiceApplication class methods.

2. Place the assembly in the same folder holding other BOS assemblies.

EntityServer Startup and Shutdown The EntityService singleton calls EntityServiceApplication methods when it starts and stops.

Page 382: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

382 | P a g e

Remember that there is only one instance of a EntityService on a given physical middle tier. This

EntityService instance creates and manages one or more EntityServer instances that do the heavy-

lifting for a set of data sources at the behest of client EntityManagers.

As this is written, there are no EntityServer equivalents to “OnServiceStartup” or

“OnServiceShutdown”. We can imagine scenarios that justify “OnServerStartup” and

“OnServerShutdown” methods as well as methods for other EntityServer events. We are prepared to

add them to the EntityServiceApplication class when we have concrete use cases for them. Please

let us know if you have such cases.

Remote Service Method Call (RSMC) Methods

EntityManager has methods InvokeServerMethod() and InvokeServerMethodAsync() that facilitate the running of

server-side-only methods. We will discuss the synchronous version first.

InvokeServerMethod()

The following two signatures are available for InvokeServerMethod():

public Object InvokeServerMethod(

String pTypeName,

String pMethodName,

params Object[] pArgs)

public Object InvokeServerMethod(

ServerMethodDelegate pDelegate,

params Object[] pArgs)

Use the first overload when the method to be invoked does not reside in any client-side assembly (e.g.,

DomainModel.dll). When the method is available client-side, the second overload can be used, and tends to be a bit

less vulnerable to the introduction of errors in the type and method names.

Example using the first overload:

C# string typeName = "DomainModel.OrderSummary,DomainModel";

string methodName = "GetNumberOfOrders";

int num = (int)_entityManager.InvokeServerMethod(typeName, methodName,

10, new DateTime(1995, 1, 1), new DateTime(1999, 1, 1)); //pArgs

Example using the second overload:

Page 383: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

383 | P a g e

C#

ServerMethodDelegate delegate =

new ServerMethodDelegate(OrderSummary.GetNumberOfOrders);

int num = (int)_entityManager.InvokeServerMethod(delegate,

10, new DateTime(1995, 1, 1), new DateTime(1999, 1, 1)); //pArgs

Here is the method to be invoked server-side. Note the [AllowRpc] attribute, without which the

InvokeServerMethod call will result in an exception.

C#

[AllowRpc]

public static Object GetNumberOfOrders(IPrincipal pPrincipal,

EntityManager pPm, params Object[] pArgs) {

return GetOrderCount(pPm, pArgs);

}

InvokeServerMethodAsync()

The asynchronous sibling of InvokeServerMethod, InvokeServerMethodAsync(), must be used in

Silverlight applications and may be used in others. It has three overloads:

public IdeaBlade.EntityModel.InvokeServerMethodOperation InvokeServerMethodAsync(IdeaBlade.EntityModel.ServerMethodDelegate serverMethod, params object[] args)

Page 384: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

384 | P a g e

public IdeaBlade.EntityModel.InvokeServerMethodOperation InvokeServerMethodAsync(IdeaBlade.EntityModel.ServerMethodDelegate serverMethod, System.Action<InvokeServerMethodOperation> userCallback, object userState, params object[] args) public IdeaBlade.EntityModel.InvokeServerMethodOperation InvokeServerMethodAsync(string typeName, string methodName, System.Action<InvokeServerMethodOperation> userCallback, object userState, params object[] userArguments)

Note that the first overload has no callback method and can only be used when the method to be

invoked resides in an assembly available client-side as well as server-side (e.g., DomainModel.dll). The

second overload is similar to the first, but provides for a callback method, and includes a UserState

parameter to identify uniquely therein the operation that has completed.

The final overload is the version that must be used when the method to be executed is available only on

a server-side assembly. It requires the specification of a callback method.

Push Notification The “push” feature allows client applications to “subscribe” to server-side code running on the BOS, and

to receive periodic user-defined notifications from the server throughout the client’s lifetime. This

feature is not available in DevForce Silverlight.

A client calls one of the EntityManager’s RegisterCallback overloads to register or subscribe to a push

service. It supplies information both about the server method to be monitored and its own method to

be called for notification of server activity. Additional parameters supply a userToken that uniquely

identifies a particular subscription request, and user-defined arguments to be passed to the service.

C# public void RegisterCallback(ServerNotifyDelegate serverDelegate, ClientNotifyDelegate clientDelegate, Object userToken, params Object[] clientArgs);

Once registered, the client remains subscribed to the service until either calling CancelCallback or

closing. The push “service” is somewhat analogous to the server-side method callable by the

InvokeServerMethod() call. The method must have a ServerNotifyDelegate signature, and will use the

passed INotificationManager to communicate with subscribers.

C# public delegate void ServerNotifyDelegate(Guid serviceKey, INotificationManager notificationManager, EntityManager serverEntityManager);

Page 385: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

385 | P a g e

The method runs on its own thread, and can perform any processing desired, including starting

additional threads or processes. The method is currently started upon first client subscription, and

stopped after the last client unsubscribes. To obtain information about its subscribers, it can use the

INotificationManager.GetSubscribers() method. This will return all current subscribers, and include the

IPrinicipal of the client and any client arguments passed. The INotificationManager.Send method is used

to send service-defined data to subscribers – allowing either a broadcast to all subscribers or to only an

individual.

These “broadcasts” are received by client applications in the method specified when registering the

callback.

C# public delegate void ClientNotifyDelegate(object pUserToken, params Object[] pArgs);

The data passed must be serializable (and currently has the same restriction as with the

InvokeServerMethod in that Entities may not be directly passed or returned).

BOS Hosting Details

Transports

The EntityService and EntityServers are all implemented as WCF services. If your BOS is hosted by either

the ServerConsole.exe or ServerService.exe, you can choose to use either HTTP (including HTTPS) or

“net.tcp” transports. (You can also choose “net.pipe” for named pipe cross process communication on a

single machine, but this is only useful during development.) Note that to use TCP you must specify a

remoteBaseUrl beginning with “net.tcp://” since this is the naming convention used by WCF.

Configuration

In the configuration file on the server, use the ObjectServer element to configure a BOS. The example

below shows the settings available, along with DevForce defaults.

Example:

Page 386: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

386 | P a g e

XML <objectServer

remoteBaseURL="http://localhost"

serverPort="9009"

serviceName="EntityService">

<serverSettings

allowAnonymousLogin="true"

loginManagerRequired="false"

sessionEncryptionKey=""

supportedClientApplicationType="UseLicense"

testMode="false"

useAspNetSecurityServices="false"

useDTC="false"

userSessionTimeout="30"

/>

</objectServer>

You will probably need to manually open the port used by your service. If using Windows Firewall, use

the “Add Port” button on the Exceptions tab. Be sure to choose a TCP port even if you‟re using the HTTP

protocol.

If you do use named pipes, remember it works on the localhost only, and do not specify a serverPort.

For more advanced scenarios, you can implement custom ServiceHostEvents and ServiceProxyEvents

sub-classes, or add a ServiceModel configuration section in your configuration files to configure the

service and proxy. Some possible reasons you might want to customize communications defaults would

be to add channel security or modify default settings (eg, for buffer sizes and timeouts).

Page 387: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

387 | P a g e

Advanced Service Configuration Details

You can skip this section if you don’t think you have the need, or the WCF knowledge, to customize the

service configuration using an app.config. Read on if you want the details to help understand what

defaults are provided.

The BOS services use a custom message encoder called GzipMessageEncoding, which transmits all

messages in compressed binary format. Because we use a custom message encoder we cannot use the

standard WCF bindings such as “basicHttpBinding”, “wsHttpBinding”, “netTcpBinding”, etc., but instead

use the “customBinding” type, which requires that we explicitly specify all elements in the binding. We

default to a customBinding stack containing the following binding elements: 1) gzipMessageEncoding

and 2) a transport as specified by the protocol scheme in the RemoteBaseURL

(HttpTransportBindingElement, HttpsTransportBindingElement, TcpTransportBindingElement, or

NamedPipeTransportBindingElement).

The BOS actually consists of at least two services: 1) the “EntityService” which functions as a factory for

creation of EntityServers, and 2) a EntityServer for each data source extension used.

For each service, an endpoint is created with an address based on the URI built from elements in the

ObjectServer section of the configuration file. The endpoint created will depend on your license; if you

have a Universal license then endpoints supporting both WinClient and Silverlight client applications will

be created. For an EntityServer, the address includes the name “EntityServer” followed by the data

source extension. The transport is assigned based on the protocol scheme, and the

MaxReceivedMessageSize is set to the maximum value – allowing for up to 2G to be transmitted in a

fetch or save. All other transport values default based on the type of transport used. The

GzipMessageEncoding uses the defaults for the underlying BinaryMessageEncoding, with the

MaxArrayLength ReaderQuota set to the maximum value – this is again to allow for large amounts of

data to be fetched and saved. Default timeout values are used for open, close, send and receive. If

using net.tcp, a ServiceThrottlingBehavior is used to set the MaximumConcurrentCalls and

MaximumConcurrentSessions to 100 (from the default of 10).

Customizing Configuration with ServiceHostEvents

Instead of, or in addition to, customizing service configuration via the serviceModel section of the

configuration file, you can intercept DevForce actions as the service is created by subclassing the

ServiceHostEvents class. The following methods can be overridden to customize configuration of your

services:

public virtual void OnEndpointCreated(ServiceEndpoint endpoint)

Page 388: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

388 | P a g e

Called when a ServiceEndpoint has been created. Override this method to customize the

endpoint. For example, you can modify the default timeout values, add security to the binding,

or add additional endpoint behaviors.

public virtual void OnServiceHostCreated(ServiceHost host)

Called when a ServiceHost has been created. Override this method to perform additional

customization of the service before it is opened. For example, you can add additional endpoints

and behaviors.

Configuration for IIS Deployment

If you’re familiar with WCF, you know that a .svc file is usually needed for each of your services in

“hosted” environments such as IIS. For the BOS this means you’ll need a file named EntityService.svc,

and a .svc file for each EntityServer based on the data source extensions used. These files should be

placed in your virtual directory. Here are examples of each:

EntityService.svc

IIS <%@ServiceHost language="C#"

Service="IdeaBlade.EntityModel.Server.RemoteEntityService"

Factory="IdeaBlade.EntityModel.Server.EntityServiceHostFactory"

%>

The service file for the EntityServer is a little trickier – the file name must be formed using the name

“EntityServer”, an underscore, and the data source extension. If no data source extension is needed,

then the file should be named “EntityServer.svc”. But there’s another wrinkle too, we need to provide

the data source extension as an argument to the Service, and specify a Factory too. Here are some

samples:

EntityServer.svc

Page 389: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

389 | P a g e

IIS <%@ServiceHost language="C#"

Service="IdeaBlade.EntityModel.Server.EntityServer"

Factory="IdeaBlade.EntityModel.Server.EntityServerHostFactory"

%>

EntityServer_Dev.svc – for a data source extension of “Dev”

IIS <%@ServiceHost language=”C#”

Service="IdeaBlade.EntityModel.Server.EntityServer, Dev"

Factory="IdeaBlade.EntityModel.Server.EntityServerHostFactory"

%>

There is another alternative to using the .svc files, which can get a little cumbersome in multi-tenant

applications. You can instead register a “virtual path provider” to have the .svc files for the BOS created

dynamically upon request. Most of the samples do this for you automatically, with some code in the

global.asax file at application start:

IIS protected void Application_Start(Object sender, EventArgs e) {

System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(

new IdeaBlade.EntityModel.Web.ServiceVirtualPathProvider());

}

We provide sample .svc, config and global.asax files in the

LearningResources\110_Deployment\Snippets\IIS Files folder.

The DevForce Client On the client, you need an app.config with service information that matches the corresponding settings

used for the server. The following app.config excerpt, for example, matches the server-side example

provided above.

Page 390: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

390 | P a g e

XML

<objectServer

remoteBaseURL="http://localhost"

serverPort="9009"

serviceName="EntityService">

<clientSettings isDistributed="true" />

</objectServer>

Be sure that the remoteBaseURL points to the correct server, and that the protocol scheme, port and

serviceName are the same as is used on the server.

As with the BOS, you can use client-side serviceModel settings in your app.config to customize your

configuration. As is true with the BOS, you should have some WCF expertise to do this, and can use the

Service Configuration Editor to help. Sample configuration files are available in the “\Learning

Resources\110_Deployment\Snippets\Sample N-tier config files” folder.

Customizing Client Configuration with ServicProxyEvents

Instead of, or in addition to, customizing client configuration via the serviceModel section of the

configuration file, you can intercept DevForce actions as the proxy is created by subclassing the

ServiceProxyEvents class. The following methods can be overridden to customize configuration of

communications from your client:

public virtual void OnEndpointCreated(ServiceEndpoint endpoint)

Called when a ServiceEndpoint has been created. Override this method if you need to

customize the endpoint. For example, you can modify the default timeout values, add security

to the binding, or add additional endpoint behaviors.

Page 391: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

391 | P a g e

public virtual void OnFactoryCreated(ChannelFactory factory)

Called when a ChannelFactory has been created. Override this method to perform additional

customization of the proxy before the channel is opened. For example, you can add additional

endpoints and behaviors.

Vista Setup

The following material applies when running the BOS under the Windows Vista operating system.

Vista setup requirements for the ServerConsole or ServerService You receive an AddressAccessDeniedException telling you that HTTP could not register your URL

because you do not have access rights to the namespace. This is caused because the process (or service)

is not running with administrator privileges and HTTP addresses are secured resources. You have two

options:

1. Run the process (or service) with an administrative account.

2. Run the Netsh command line tool to register the namespace with the account. The steps are as follows:

a. Open a command prompt using “Run as administrator” and enter:

b. netsh http add urlacl url=http://+:9009/ user=DOMAIN\USERNAME

...where “9009” is the port you are using for the BOS, and DOMAIN\USERNAME is the system

account to be granted access.

Also see http://blogs.msdn.com/drnick/archive/2006/10/16/configuring-http-for-windows-vista.aspx for

more information.

Vista setup requirements for IIS Since the BOS is a WCF service, you must ensure that IIS, WCF and the WCF activation component are

installed and registered. Both WCF and IIS must be installed for IIS-hosted WCF services to function

correctly. The installation process for the .NET Framework 3.0 automatically registers WCF with IIS if IIS

is already present on the machine. If IIS is installed after the .NET Framework 3.0, an additional step is

required to register WCF with IIS and ASP.NET. You can do this as follows, depending on your operating

system:

Page 392: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

392 | P a g e

Windows XP SP2 and Windows Server 2003: Use the ServiceModelReg.exe tool to register WCF with

IIS: To use this tool, type ServiceModelReg.exe /i /x at a command prompt.

Windows Vista: Install the Windows Communication Foundation Activation Components subcomponent

of the .NET Framework 3.0. To do this, in Control Panel, click Add or Remove Programs and then

Add/Remove Windows Components. This activates the Windows Component Wizard.

Troubleshooting

Worked in 2-Tier, Strange Errors in n-Tier Applications generally do not have 2-tier dependencies that cause an n-tier application to fail. The

hardest part of going n-tier is setting up the environment of the middle tier, a topic covered in the

Deployment chapter.

We assume in this section that you have established your ability to connect to the middle tier (the

Business Object Server) and can see some interaction between client and server.

Mismatched Client-side and Server-side Model Libraries DevForce applications must have identical Model-related DLLs on both client and middle tiers.

Mismatched class libraries are the number one cause of strange n-tier behavior.

The UI class libraries do not belong on the middle tier. This topic concerns the Model DLLs such as those

holding your business object entities. They could include Model helper assemblies if you have them,

such as a separate library holding Id generation or Login-logic.

Proper versioning of each build is the best protection against mismatched DLLs. .NET detects the

mismatch and reports it the moment that a business object entity starts moving across the tiers.

Unfortunately, many people neglect to bump the version number with each new build. We forget to do

it too.

Perhaps there is no difference in the code but the build was done with different configurations: a debug

configuration in one case and a release configuration in the other.

We get away with it for awhile and then one day we publish a business object model for clients but

neglect to deploy it to the middle tier. The client and server model DLLs have the same version numbers

so .NET permits them to interoperate. But they are not actually the same and a subtle incompatibility

yields weird results or an exception.

Do confirm that the Model DLLs on both the client and server have the same version number, the same file

date, and the same file size.

Page 393: Dev Force 2010 Developers Guide

IdeaBlade DevForce Business Object Server

393 | P a g e

Please understand that technical support will ask for evidence that this is so. Time and again we discover

that the DLLs are not the same despite numerous verbal assurances that they are. We may ask you to

re-deploy the libraries directly from the client side just to be sure. We appreciate your patience.

Mismatched IdeaBlade Class Libraries The IdeaBlade class library versions must be the same on both client and middle tiers. If you’ve recently

upgraded DevForce, you should confirm that the same DLLs are also on the server.

“Maximum concurrent users limit met or exceeded” Exception You might see this error in several situations when you have a limited-user license. It may seem

incorrect, since in some cases you know that you do not have more concurrent users than your license

allows. So why are you getting the error? DevForce decrements the user count under two conditions:

when Logout is called, and

after 30 minutes of inactivity.

Be sure to call EntityManager.Logout() when you close your application, and you’ll avoid this problem.

During testing, you may find that you want or need to exceed your user license. DevForce provides this

option with a setting in the server’s config file. Set the testMode attribute to true, like so:

XML <objectServer>

<serverSettings testMode="true" />

</objectServer>

Test mode supports an unlimited number of users for a period of one hour.

Check the debuglog.xml output on your BOS for messages relating to this feature. You will see messages

both when test mode starts, and again when the test mode interval has elapsed. After the test mode

interval has elapsed, all connection attempts by clients will be refused, and these refusals will be seen in

the log also.

After the test mode interval has elapsed, you will need to restart the BOS if you need to continue in this

mode.

To turn test mode off, either remove the attribute from the <ideaBlade.configuration> element, or set

the value to “false”.

Page 394: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

394 | P a g e

Disconnected Applications

Disconnected Applications ......................................................................................................... 394

State of the Release Candidate Documentation ................................................................................................. 394

Introduction ....................................................................................................................................................... 394

Running Offline ................................................................................................................................................. 396

Securing Offline Data ........................................................................................................................................ 409

State of the Release Candidate Documentation

We are working hard to update all of our documentation from DevForce 2009 to DevForce 2010, .NET 4,

and Silverlight 4. During this conversion, you may find some sections that are out of date, but you should

be able to get many of the examples to work, with small modifications, by checking against the API

Documentation for the current method signatures.

Introduction Many applications enable end users to work on data while offline. Users of Microsoft Outlook, for

example, can review and create new emails while on a plane or in some other disconnected location.

Changes can be saved back to the host database when the connection is restored.

DevForce can help build such applications.

Client-side caching makes it possible to operate disconnected from the host for extended periods,

insulating the client from connection problems. The end-user can keep working as long as the client

application itself keeps running.

The application may not be able to perform all of its functions while disconnected. It is up to the developer

to regulate what can and cannot be done in a disconnected state.

The end-user must shut down eventually. What to do if there is no connection then or if the application

will be resumed without access to the host? The application must be able to save “state” locally and

restore such state when the application restarts.

DevForce provides three essentials for an application that can thrive off-line:

A serialized representation of entity state – the EntityCacheState.

The ability to save that state somewhere – the SaveCacheState methods.

Page 395: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

395 | P a g e

The ability to retrieve and restore entity state – the RestoreCacheState methods.

The EntitySet contains a binary serialization of entity persisted state and information related to those

entities. While designed to hold the contents of an entire EntityManager it can as easily contain selected

entities (such as those which have been modified – a point to which we will return later).

The EntitySet does not preserve non-persisted state such as the values of custom fields you‟ve added to

your business objects. (Values of custom properties that are based on persisted fields will, of course, be

computed as needed from their definitions in your business classes.)

DevForce EntityManager’s “EntitySet” methods shuttle business object data between the

EntityManager’s in-memory entity cache and local storage.

The SaveCacheState methods convert object state to “EntitySet” format.

C#

void SaveCacheState(string fileName);

void SaveCacheState(IEnumerable entities, string fileName)

void SaveCacheState(Stream stream, bool closeOnExit)

void SaveCacheState(IEnumerable entities, Stream stream, bool closeOnExit)

VB

Sub SaveCacheState(fileName As String)

Sub SaveCacheState(entities As IEnumerable, fileName As String)

Sub SaveCacheState(stream As Stream, closeOnExit As Boolean)

Sub SaveCacheState(entities As IEnumerable, stream As Stream, closeOnExit As Boolean)

SaveCacheState can preserve the entire entity cache, or just some selected objects. The destination file

may reside anywhere in the local file system although it is often wise to save to the user's "Isolated

Storage". The "stream" signatures facilitate piping the data through intermediate filters before they get

to their ultimate destination. For example, we might want to flow object data through an encryption

filter before storing them. You can also append custom bytes to the same stream if you're up to

managing that yourself.

Page 396: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

396 | P a g e

When the user re-launches the application, it should locate the saved file (or stream) and restore its contents to

the EntityManager’s entity cache, using one of the following methods (in

EntityManager.CacheStateManager):

C#

void RestoreCacheState(string fileName)

void RestoreCacheState(string fileName, RestoreStrategy strategy)

void RestoreCacheState(Stream stream, RestoreStrategy strategy, bool closeOnExit)

void RestoreCacheState(EntityCacheState entityCacheState)

void RestoreCacheState(EntityCacheState entityCacheState, RestoreStrategy strategy)

VB

Sub RestoreCacheState(String fileName)

Sub RestoreCacheState(String fileName, RestoreStrategy strategy)

Sub RestoreCacheState(Stream stream, RestoreStrategy strategy, Boolean closeOnExit)

Sub RestoreCacheState(EntityCacheState entityCacheState)

Sub RestoreCacheState(EntityCacheState entityCacheState, RestoreStrategy strategy)

The RestoreStrategy parameter prescribes how to handle incoming cache state objects that already

exist in the target EntityManager’s cache. The default RestoreStrategy preserves the cache's pending

business objects changes – additions, modifications, and deletions.

Running Offline Offline applications are the result of careful design. They can’t be generated automatically. The

developer must think through:

What entities must be available offline? How much data can we keep locally?

What entities can be changed? Which entities can be added or deleted?

What operations are permitted while offline?

How do we communicate to users these differences between online and offline capabilities?

How should the application transition back online?

How do we resolve concurrency conflicts when it is much more likely that another user will have modified a

record mapped to an object we changed?

Are there security consideration? What if the laptop is stolen?

Preparing for Offline Data

Page 397: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

397 | P a g e

We can only work disconnected with data that are available locally. While disconnected, queries and

object navigation can only access cached entities. The application must anticipate the user's

disconnected data needs.

Many applications pre-fill the cache with entities the user will need offline before saving that cache and

disconnecting.

“Saving” While Offline While offline, the EntityManager’s SaveChanges() method can’t help you: there is no connection

pathway to the datasource. But DevForce provides a pair of additional functions, SaveCacheState() and

RestoreCacheState(), to give you a means of storing the contents of the EntityManager’s cache to, and

retrieving them from, a local disk file.

Saving the Cache Contents to a Local Disk File

The EntityManager’s SaveCacheState() method writes the contents of the cache to a binary,

unencrypted local disk file. There are several overloads:

Overload Description

Page 398: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

398 | P a g e

C#

public void SaveCacheState(

string pFileName

)

VB

Overloads Public Sub SaveCacheState( _

ByVal pFileName As String _

)

Stores the state of this EntityManager

and all cached entities to a file system

file.

C#

public void SaveCacheState(

IEnumerable pDataRows,

string pFileName

)

VB

Overloads Public Sub SaveCacheState( _

ByVal pDataRows As IEnumerable, _

ByVal pFileName As String _

)

Stores the state of this EntityManager

and the specified entities to a file

system file.

By using a non-generic BindableList

or EntityList for the pDataRows

parameter, you can mix disparate

Entity types.

C#

public void SaveCacheState(

Stream pStream,

bool pCloseOnExit

)

VB

Overloads Public Sub SaveCacheState( _

ByVal pStream As Stream, _

ByVal pCloseOnExit As Boolean _

)

Stores the state of this EntityManager

and all cached entities to a stream.

Page 399: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

399 | P a g e

C# public void SaveCacheState(

IEnumerable pDataRows,

Stream pStream,

bool pCloseOnExit

)

VB Overloads Public Sub SaveCacheState( _

ByVal pDataRows As IEnumerable, _

ByVal pStream As Stream, _

ByVal pCloseOnExit As Boolean _

)

Stores the state of this EntityManager

and the specified entities to a stream.

By using a non-generic BindableList

or EntityList for the pDataRows

parameter, you can mix disparate

Entity types.

Here is some sample code that uses the first-listed overload of SaveCacheState() to write the cache

contents to a disk file:

Page 400: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

400 | P a g e

C#

private void StoreOffLine() {

string path = Application.UserAppDataPath + "\\MyLocalData.bin";

try {

if (! (Directory.Exists(Application.UserAppDataPath))) {

Directory.CreateDirectory(Application.UserAppDataPath);

}

_entityMgr.CacheStateManager.SaveCacheState(path);

MessageBox.Show("Changes saved to local storage");

}

catch (Exception ex) {

MessageBox.Show(ex.Message);

}

}

VB

Private Sub StoreOffLine()

Dim path As String = Application.UserAppDataPath + "\MyLocalData.bin"

Try

If Not Directory.Exists(Application.UserAppDataPath) Then

Directory.CreateDirectory(Application.UserAppDataPath)

End If

_entityMgr.CacheStateManager.SaveCacheState(path)

MessageBox.Show("Changes saved to local storage")

Catch ex As Exception

MessageBox.Show(ex.Message)

End Try

End Sub

You’ll see an example of one of the overloads that writes to a MemoryStream in the section, “Saving the

Cache Contents to an Encrypted Local Disk File”.

Page 401: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

401 | P a g e

Retrieving the Cache Contents from a Local Disk File

The EntityManager’s RestoreCacheState() method reads the contents of a binary, unencrypted local disk

file into the local cache. As with SaveEntitytSet(), there are several overloads:

Page 402: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

402 | P a g e

Overload Description

C#

public void

RestoreCacheState(

EntitySet pEntitySet

)

VB

Overloads Public Sub

RestoreCacheState( _

ByVal pEntitySet As

EntitySet _

)

Merges an EntitySet into this EntityManager using the

NormalRestoreStrategy.

A normal RestoreStrategy replaces the SaveOptions and

QueryOptions in the current cache with those saved in the

incoming EntitySet; and uses a MergeStrategy of

PreserveChanges.

C#

public void

RestoreCacheState(

EntitySet pEntitySet,

RestoreStrategy

pStrategy

)

VB

Overloads Public Sub

RestoreCacheState( _

ByVal pEntitySet As

EntitySet, _

ByVal pStrategy As

RestoreStrategy _

)

Merges an EntitySet into this EntityManager using the

RestoreStrategy specified.

Page 403: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

403 | P a g e

C#

public void

RestoreCacheState(

string pFileName

)

VB

Overloads Public Sub

RestoreCacheState( _

ByVal pFileName As

String _

)

Restores entities from a file system file into this

EntityManager using the NormalRestoreStrategy.

C#

public void

RestoreCacheState(

string pFileName,

RestoreStrategy

pStrategy

)

VB

Overloads Public Sub

RestoreCacheState( _

ByVal pFileName As

String, _

ByVal pStrategy As

RestoreStrategy _

)

Restores entities from a file system file into this

EntityManager using the RestoreStrategy specified.

Page 404: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

404 | P a g e

C#

public void

RestoreCacheState(

Stream pStream,

RestoreStrategy

pStrategy,

bool pCloseOnExit

)

VB

Overloads Public Sub

RestoreCacheState( _

ByVal pStream As

Stream, _

ByVal pStrategy As

RestoreStrategy, _

ByVal pCloseOnExit As

Boolean _

)

Restores entities from a stream into this PeristenceManager

using the RestoreStrategy specified.

Note that several of the overloads of RestoreCacheState() take a RestoreStrategy. Here some sample

code to instantiate and use one of those:

C#

RestoreStrategy aRestoreStrategy =

new RestoreStrategy(true,true,MergeStrategy.PreserveChanges);

_entityManager.CacheStateManager.RestoreCacheState(path, aRestoreStrategy);

VB

Dim aRestoreStrategy As New RestoreStrategy( _

pRestoreSaveOptions:=True, pRestoreQueryStrategy:=True, _

pMergeStrategy:=MergeStrategy.PreserveChanges)

Page 405: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

405 | P a g e

_entityManager.CacheStateManager.RestoreCacheState(path, aRestoreStrategy)

The VB code shows the parameter meanings most clearly: you get to specify whether you wish to

replace the SaveOptions and QueryOptions in the current cache with those saved in the incoming

EntitySet; and just how you want incoming entities merged with entities that may already be present in

the cache at the time of the restore.109

The following code uses the first-listed overload of RestoreCacheState() to load the contents of a stored

EntitySet to the local cache:

C#

private void LoadOffLine() {

string path = Application.UserAppDataPath + "\\MyLocalData.bin";

if (File.Exists(path)) {

try {

_entityManager.RestoreCacheState(path);

_employees = entityManager.Employees;

_employeesBS.DataSource = _employees;

_managersBS.DataSource = _employees;

}

catch (Exception pException) {

MessageBox.Show(pException.Message);

File.Move(path, path + "\\\\" + DateTime.Now.ToString());

MessageBox.Show("Local data file not found!");

}

}

}

VB

109 MergeStrategies include NotApplicable, OverwriteChanges, PreserveChanges, PreserveChangesUnlessObsolete, and

PreserveChangesUpdateOriginal.

Page 406: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

406 | P a g e

Private Sub LoadOffLine()

Dim path As String = Application.UserAppDataPath + "\MyLocalData.bin"

If File.Exists(path) Then

Try

_entityManager.RestoreCacheState(path)

_employees = _entityManager.Employees

Me._employees BS.DataSource = _employees

Me._managersBS.DataSource = _employees

Catch pException As Exception

MessageBox.Show(pException.Message)

File.Move(path, path + "\\" + DateTime.Now.ToString())

MessageBox.Show("Local data file not found!")

End Try

End If

End Sub

You’ll see an example of one of the overloads that reads from a MemoryStream in the section,

“Retrieving the Cache Contents from an Encrypted Local Disk File”.

Saving a Subset of the Cache to Local Storage

New entities, and entities with changes, constitute the most critical data in the cache, from a

persistence viewpoint. The application could terminate unexpectedly due to a power loss or unhandled

exception. Absent a regular, automatic backup of unsaved changes, they would be lost. On the other

hand, repeated saves of the entire cache could become quite cumbersome with a large cache, adversely

impacting your user’s UI experience.

Wouldn’t it be nice to be able save to local storage, not the entire contents of the cache, but a subset

thereof – say, just the new and modified items? Can you think of other reasons you’d like to be able to

save only a subset of the cache to local storage? Fortunately, the overloads of SaveCacheState() that

take a DataRows parameter permits you to do just this.

Here’s an example that saves all entities with pending (unsaved) changes:

Page 407: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

407 | P a g e

C#

private void SaveChangesLocally() { // C#

string fileName = "AppPendingChanges"; // ToDo: use encrypted stream instead

DataRowState changes =

DataRowState.Added | DataRowState.Deleted | DataRowState.Modified;

MainPm.Manager.SaveCacheState(MainPm.Manager.GetEntities(changes), fileName);

}

VB

Private Sub SaveChangesLocally() ' VB

Dim filename As String = "AppPendingChanges" ' ToDo: use encrypted stream

Dim changes As DataRowState = _

DataRowState.Added Or DataRowState.Deleted Or DataRowState.Modified

MainPm.Manager.SaveCacheState(MainPm.Manager.GetEntities(changes), fileName)

End Sub

Of course if we are to run off-line, it will be important to save the unmodified entities too. But we can do

that infrequently to one file (“AppUnmodified”) and subsequently save just the pending changes to a

second file (“AppPendingChanges”). We (over)write that second file frequently. When we re-launch the

application, after intentional or accidental shutdown, we rebuild the cache state by loading first the

“AppUnmodified” file and then the “AppPendingChanges” file.

Autosave

Even if we don’t want our application to run disconnected, we may still want to protect users from

catastrophic failures. Microsoft Word has an “autosave” feature that preserves changes made in the last

minutes; it can discover that the application did not shut down properly and offer the user the chance to

restore those changes. Wouldn’t that be a great feature for our application?

The easiest approach is to save changes to the host every few minutes. But that may not be possible.

The changes may not pass validation checks and there may be no good place on the host to save invalid

data.

Many of the techniques for offline applications are suitable for coping with this problem. In brief, we can

Set up an “autosave” timer

Save (and encrypt) just the changed entities to a recovery file.

Reset the recovery file everytime we succeed in saving to the host.

Page 408: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

408 | P a g e

Establish secure and consistent recovery techniques that detect improper shutdown and restore to cache the

cache state file with pending changes.

Temporary Ids and Off-Line “Saves”

DevForce typically assigns newly created objects a temporary, primary key “Id”. The objects retain this

temporary id until they are saved to the data store. DevForce keeps a list of these temporary ids.

We can still reference these new objects in other objects. For example, if we create a new order and

new order line items that attach to them, those line items have references to their parent order –

references that make use of the temporary id.

Just before it saves objects to the data store, DevForce fixes up all temporary Ids – including all

references to these ids – by replacing the temporary values with permanent values. The fix-up process

relies upon that list of temporary ids; it forms the basis of the fix-up map that marries a permanent id

value to each temporary id value.

What can we do when the user pushes the save button while the application is disconnected? We can’t

reach the data store so we can’t acquire permanent ids. Without permanent ids, we can’t fix up the

temporary ids.

We could tell the user to wait until we can reconnect. That’s not a good experience if the application will

be off-line for more than a few seconds.

The obvious answer is to save the new and changed entities to local disk with SaveCacheState. We can

safely shut down the application if we have to, then re-launch and restore the cache later with

RestoreCacheState.

If DevForce only saved entities, we would be unable to fix-up the temporary ids when we restored. We

need the list of temporary ids to do the fix-up. Fortunately, DevForce saves the list of temporary ids with

the saved EntitySet. It has always done so.

However, it used to be that you had only one shot at temporary id list restoration. Either the target

cache or the EntityCacheState could have temporary ids but not both. More importantly, we could

restore only from a single saved EntityCacheState. We were unable to restore multiple CacheStates

if more than one of them had temporary ids.

Why might we need to save and restore multiple CacheStates? Follow along:

1. We start the application and get some objects.

2. We go off-line.

3. We press [save]; the application stores the pending added and changed entities to EntitySetSave1.

4. The application “pretends” that these items have been saved by marking them as unmodified in the cache. Note that such “unmodified” data may have temporary ids.

Page 409: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

409 | P a g e

5. We make more changes (perhaps even to entities we created earlier) and save again; the app stores locally to EntitySetSave2.

6. We make more changes.

7. We decide to shut down but we don’t want to commit these changes so we do not save.

8. The application saves all unmodified data to disk in EntitySetUnmodified.

9. The application saves all pending added and changed entities to EntitySetMods.

10. The application shuts down.

11. We re-launch a few hours later. The application can connect to the data store and is ready to save our changes.

12. The application restores EntitySetUnmodified to the entity cache.

13. It restores EntitySetSave1 and restores their temporary ids in the process. The restoration overwrites objects in the cache, refashioning some unchanged entities as either modified or added.

14. It saves these entities, performing the id fix-up first.

15. It erases the now expired EntitySetSave1.

16. Next it restores EntitySetSave2 and its temporary ids.

17. It saves this second batch of entities and erases the file as before.

18. Finally, it restores the EntitySetMods.

19. It does not save these changes because we did not commit them. The application has now restored the entity cache as it was when we shut down with the important

difference that our saves have been committed to the permanent store.

Securing Offline Data Saving your Entity Manager cache to an unencrypted local disk file on your laptop creates an obvious

security vulnerability for your data. Fortunately, it’s easy to encrypt the file.

Saving the Cache Contents to an Encrypted Local Disk File

To encrypt the contents of the cache while store, you use an overload of SaveCacheState () that write

the cache to a MemoryStream:

Page 410: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

410 | P a g e

C#

private void StoreOffLine() {

MemoryStream aMemoryStream = new MemoryStream();

string binaryCacheDataAsString = null;

string encryptedBinaryCacheDataAsString = null;

string filePath = null;

string folderPath = System.Windows.Forms.Application.UserAppDataPath;

if (! (IsValidUserAppDataPath(folderPath))) {

MessageBox.Show("Unable to write data to folder " + folderPath + ".");

return;

}

else {

filePath = folderPath + "\\DevForceCache.bin";

StreamWriter aStreamWriter = new StreamWriter(filePath, false);

try {

_entityMgr.CacheStateManager.SaveCacheState(aMemoryStream, true);

binaryCacheDataAsString = Convert.ToBase64String(aMemoryStream.GetBuffer());

encryptedBinaryCacheDataAsString =

IdeaBlade.Util.CryptoFns.SimpleDESEncrypt(

binaryCacheDataAsString, mEncryptionKey);

aStreamWriter.Write(encryptedBinaryCacheDataAsString);

}

catch (Exception ex) {

MessageBox.Show(ex.Message);

}

finally {

aStreamWriter.Flush();

aStreamWriter.Close();

string msg =

"Local cache contents saved in encrypted form to local storage at: ";

MessageBox.Show(msg + filePath);

}

}

}

#endregion

Page 411: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

411 | P a g e

VB

Private Sub StoreOffLine()

Dim aMemoryStream As New MemoryStream

Dim binaryCacheDataAsString As String

Dim encryptedBinaryCacheDataAsString As String

Dim filePath As String

Dim folderPath As String = System.Windows.Forms.Application.UserAppDataPath

If Not IsValidUserAppDataPath(folderPath) Then

MessageBox.Show("Unable to write data to folder " + folderPath + ".")

Return

Else

filePath = folderPath + "\DevForceCache.bin"

Dim aStreamWriter As New StreamWriter(filePath, False)

Try

_entityMgr.CacheStateManager.SaveCacheState(aMemoryStream, True)

binaryCacheDataAsString = Convert.ToBase64String(aMemoryStream.GetBuffer())

encryptedBinaryCacheDataAsString = _

IdeaBlade.Util.CryptoFns.SimpleDESEncrypt(binaryCacheDataAsString,

mEncryptionKey)

aStreamWriter.Write(encryptedBinaryCacheDataAsString)

Catch ex As Exception

MessageBox.Show(ex.Message)

Finally

aStreamWriter.Flush()

aStreamWriter.Close()

Dim msg As String = _

"Local cache contents saved in encrypted form to local storage at: "

MessageBox.Show(msg + filePath)

End Try

End If

End Sub

#end Region

Private Function IsValidUserAppDataPath(ByVal pFolderPath) As Boolean

Dim retVal As Boolean

Page 412: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

412 | P a g e

The method directs the SaveCacheState () method to write to a memory stream; converts that memory

stream to a Base64String; encrypts the Base64String using an encryption function, SimpleDESEncrypt(),

from the IdeaBlade.Util.CryptoFns namespace; and writes the encrypted string to disk.

Whence the Encryption Key?

In the above code, an encryption key is stored in a class-scoped variable, mEncryptionKey. Where did

the value for that variable come from?

That’s up to you, but it is much better if it isn’t stored anywhere, as such. One good scheme is to use

the current user’s password as the encryption key. If the user can log in (to Windows, or your app,

whichever applies), she can get access to the decrypted data. Otherwise, not.

Retrieving the Cache Contents from an Encrypted Local Disk File

Here is the corresponding code to read back the encrypted EntityCacheState file:

Page 413: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

413 | P a g e

C#

private void LoadOffLine() {

MemoryStream aMemoryStream = null;

string encryptedBinaryCacheDataAsString = null;

byte[] binaryCacheDataAsString = null;

string path = System.Windows.Forms.Application.UserAppDataPath + "\\";

try {

//Read the file

StreamReader aStreamReader = new StreamReader(path + "MyLocalData.bin");

try {

//Convert the encrypted data to a string

encryptedBinaryCacheDataAsString = aStreamReader.ReadToEnd();

//Decrypt the encrypted string

binaryCacheDataAsString =

Convert.FromBase64String(IdeaBlade.Util.CryptoFns.SimpleDESDecrypt(

encryptedBinaryCacheDataAsString, mEncryptionKey));

//Read the decrypted string into a MemoryStream

aMemoryStream = new MemoryStream(binaryCacheDataAsString);

//Restore the EntitySet from the decrypted MemoryStream

_entityMgr.CacheStateManager.RestoreCacheState(

aMemoryStream, RestoreStrategy.Normal, true);

}

catch (Exception ex) {

MessageBox.Show(ex.Message);

}

finally {

//Close the reader and reload the Employee EntityList

aStreamReader.Close();

mEmployees = _entityMgr. Employees;

this.mEmployeesBS.DataSource = mEmployees;

this.mManagersBS.DataSource = mEmployees;

}

}

catch (Exception pException) {

MessageBox.Show(pException.Message);

Page 414: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

414 | P a g e

VB

Private Sub LoadOffLine()

Dim aMemoryStream As MemoryStream

Dim encryptedBinaryCacheDataAsString As String

Dim binaryCacheDataAsString As Byte()

Dim path As String = System.Windows.Forms.Application.UserAppDataPath & "\"

Try

'Read the file

Dim aStreamReader As New StreamReader(path & "MyLocalData.bin")

Try

'Convert the encrypted data to a string

encryptedBinaryCacheDataAsString = aStreamReader.ReadToEnd()

'Decrypt the encrypted string

binaryCacheDataAsString = _

Convert.FromBase64String(IdeaBlade.Util.CryptoFns.SimpleDESDecrypt( _

encryptedBinaryCacheDataAsString, mEncryptionKey))

'Read the decrypted string into a MemoryStream

aMemoryStream = New MemoryStream(binaryCacheDataAsString)

'Restore the EntitySet from the decrypted MemoryStream

_entityMgr.CacheStateManager.RestoreCacheState(

aMemoryStream, RestoreStrategy.Normal, True)

Catch ex As Exception

MessageBox.Show(ex.Message)

Finally

'Close the reader and reload the Employee EntityList

aStreamReader.Close()

mEmployees = _entityMgr.Employees

Me.mEmployeesBS.DataSource = mEmployees

Me.mManagersBS.DataSource = mEmployees

End Try

Catch pException As Exception

MessageBox.Show(pException.Message)

End Try

End Sub

Page 415: Dev Force 2010 Developers Guide

IdeaBlade DevForce Disconnected Applications

415 | P a g e

The above method assigns the encrypted file to a StreamReader; reads the encrypted stream into an

encrypted string; decrypts the encrypted string using the IdeaBlade.Util.CryptoFns.SimpleDESDecrypt()

method; assigns the decrypted string to a MemoryStream; and calls RestoreCacheState, passing it the

MemoryStream.

Re-synchronizing changes When the application obtains a server connection, it can synchronize local objects with the remote data

source. It can save local pending changes, relying upon DevForce optimistic concurrency checking to

prevent overwriting other users’ changes. It might refresh local copies of business objects with objects

updated by other users previously.

Login while offline When logging in while offline, there is no host to authenticate the user. That can still be okay, since of

course there will be no access to the database or other non-local datasource in that circumstance. If

you are using the user’s password as the encryption key, then the login will constitute the credential

that permits the user to see even the locally stored data.

You may wish to keep the user credentials around for scenarios in which a session begins offline but

becomes connected when connection is again possible. In that circumstance you should keep a hash of

the password, rather than the password inself, in memory (to prevent any rogue module from accessing

the password in memory). The hashed password can be the encryption key for the locally stored

EntitySet.

Page 416: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

416 | P a g e

Security

Security ....................................................................................................................................... 416

State of the Release Candidate Documentation ................................................................................................. 416

Introduction ....................................................................................................................................................... 416

Authentication ................................................................................................................................................... 417

Authorization ..................................................................................................................................................... 420

Query and Save Interception ............................................................................................................................. 422

Encryption ......................................................................................................................................................... 425

ASP.NET Security Integration .......................................................................................................................... 425

State of the Release Candidate Documentation

We are working hard to update all of our documentation from DevForce 2009 to DevForce 2010, .NET 4,

and Silverlight 4. During this conversion, you may find some sections that are out of date, but you should

be able to get many of the examples to work, with small modifications, by checking against the API

Documentation for the current method signatures.

Introduction DevForce has implemented security protocols that ensure confidentiality and data integrity.

These protocols fall into three categories, all them necessary for comprehensive security.

Authentication Verifies the identity of every participant in the system

Authorization Role-based security restricts access and update to data based on permissions

granted to a logged-in application user.

Encryption Prevents unauthorized third parties from eavesdropping on a connection.

Page 417: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

417 | P a g e

Authentication There are two sides to the authentication equation: the client must authenticate the server and the

server must authenticate the client.

User Identity Verification - Login The client must log into a DevForce Business Object Server before it will deliver any Entity Services.

DevForce provides a login mechanism that builds on the developer’s implementation of .NET's

IPrincipal interface. That interface is robust and supportive of a wide range of application login

schemes including Windows authentication and LDAP. ASP.NET authentication is also specifically

supported by DevForce.

After login, the client and server exchanges are accompanied by a tamper-proof certification called a

SessionBundle. The server insists on seeing it with every client request.

The server typically caches the SessionBundle for performance. However, the cached version is not

essential and can be (re)constructed as needed so that load-balanced, multi-server applications can be

truly stateless and do not have to be session-aware.

Client-side, your code can access the authenticated IPrincipal through the EntityManager.Principal

property.

Implementing IEntityLoginManager

Verifying a user’s identity can be easily accomplished using the IEntityLoginManager interface. Add a

class which implements the interface methods, and ensure that DevForce can find the class by placing

the assembly in the same folder as the executable. The assembly needs to be deployed only on the

server, since login processing in an n-tier application is not performed on the client.

The interface contains two methods: Login() and Logout().

Login()

Your Login method will be passed the ILoginCredential that the client used in the EntityManager.Login()

call. Note that if you call EntityManager.Login() without credentials, or allow an implicit login to take

place, that the credential will be null: your code should handle this.

An EntityManager is also passed to the method to allow you to easily query your domain model. The

EntityManager here is a special, server-side EntityManager instance which is already “connected” to the

EntityService and does not require a login. You can execute queries from this EntityManager if

necessary. This EntityManager is not an instance of your sub-typed DomainModelEntityManager, but

rather of the base EntityManager class. You can, however, still easily create queries. For example:

Page 418: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

418 | P a g e

C#

// Build an EntityQuery from scratch.

var query1 = new EntityQuery<IIdentity>("Customers", entityManager);

// Construct a domain-specifc EntityManager and use its query methods.

DomainModelEntityManager em = new DomainModelEntityManager(entityManager);

var query2 = em.Customers;

From your Login() method, you should return a type implementing IPrincipal. Common

implementations are GenericPrincipal, WindowsPrincipal, or UserBase; but any serializable type is

allowed. If the credentials supplied are not valid, you should throw a LoginException indicating the cause

of the failure. On the server, DevForce will cache the IPrincipal that is returned, and will also encrypt it

in the token it returns to the client. The client can retrieve the IPrincipal using the

EntityManager.Principal property. On both client and server, the IPrincipal can be used for subsequent

user authorization checking.

Logout()

The Logout method allows you to perform any processing needed when the user logs off. You might

find this useful to perform session-level auditing or resource cleanup. Even if you have no need for

logout processing, you must still implement an empty method.

Sample

Here’s a sample class implementing the IEntityLoginManager interface. It returns a GenericPrincipal

from the Login() method, and requires no special Logout processing.

C#

public class LoginManager : IEntityLoginManager {

public IPrincipal Login(ILoginCredential credential, EntityManager entityManager) {

// Disallow null credentials and throw a LoginException

if (credential == null) {

throw new LoginException(

LoginExceptionType.NoCredentials, "Credentials not supplied");

}

// Build a simple Principal/Identity. You may return any serializable

// types implementing IPrincipal.

GenericIdentity identity = new GenericIdentity(credential.UserName);

GenericPrincipal principal = new GenericPrincipal(identity, new string[] { });

return principal;

}

public void Logout(IPrincipal principal, EntityManager entityManager) {

// No special processing needed.

}

}

Customizing the Login process

Page 419: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

419 | P a g e

ILoginCredential. Any serializable type implementing the ILoginCredential interface can be used. The

LoginCredential, and the FormsAuthenticationLoginCredential for Silverlight applications, are the

DevForce-supplied implementations. The credential supplied in the EntityManager.Login() call is the

credential received in the IEntityLoginManager.Login(). The class defining the ILoginCredential must be

available on both client and server, and must be discoverable by DevForce.

IPrincipal. Any serializable type implementing System.Security.Principal.IPrincipal can be returned from

the Login() method. The object is returned to the client and is available via the EntityManager.Principal

property on the EntityManager instance on which Login was called. The IPrincipal is also available to

other server methods. The class defining the IPrincipal must be available on both client and server and

discoverable by DevForce.

LoginException. Any serializable type extending LoginException can be thrown for login failures. To

ensure that your custom exception is received correctly on the client, you must also implement a

constructor accepting the message, and a constructor accepting a dictionary of any custom properties.

DevForce will automatically serialize any custom properties via a Dictionary<string, object>, and will call

the appropriate constructor when building the exception on the client. The class defining the custom

exception must be available on both client and server and discoverable by DevForce. For example:

C#

[Serializable]@public class CustomLoginException

: LoginException {

public CustomLoginException(string message, int

severity) : base(message) {

Severity = severity;

}

public CustomLoginException(string message,

Dictionary<string, object> userData) :

base(message) {

Severity = (int)userData["Severity"];

}

public int Severity {

get;

private set;

}

}

Ensuring That Login Will Fail

If an IEntityLoginManager Class Is Not

Deployed

By default in DevForce, login succeeds when the

application is launched while disconnected, or if DevForce

can’t find the application’s implementation of the IEntityLoginManager interface. This is so even if the

user enters incorrect credentials, because there’s no login manager to detect this.

This behavior is by design. We want to make it easy for the developer to move forward with application

development without worrying about security features in the early going. In production use, on the

Discoverability

DevForce will usually find your

custom implementations without any

extra work on your part. In WinClient

and ASP.NET applications, just make

sure the assemblies containing your

custom types are available in the

same folder as the executable (or the

bin folder). In Silverlight applications,

the assemblies should be in the

application’s XAP.

Page 420: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

420 | P a g e

other hand, a security hole is created if an IEntityLoginManager class is expected by the application, but

somehow has not been properly deployed. Absent other countermeasures, this could result in

successful logins by unauthorized users.

There is a property in the IdeaBlade Configuration File called LoginManagerRequired.

LoginManagerRequired defaults to false, preserving the loose checking that is desirable during

development. If, on the other hand, the property has been set to True and the EntityServer cannot

find an implementation of IEntityLoginManager, the EntityServer throws a LoginException of type

LoginExceptionType.NoLoginManager. This logic applies whether the EntityServer is executing on

the client (as it is in 2-tier or offline mode) or on the Business Object Server.

XML

<objectServer>

<serverSettings loginManagerRequired="true" />

</objectServer>

If your application expects an IEntityLoginManager class to authenticate logins, you should take care in

any production version to set the LoginManagerRequired property to True in the configuration file

(app.config or web.config). This will serve as a second line of defense against any failure to properly

deploy the IEntityLoginManager class.

Disallowing Anonymous Access

When creating demos and other sample applications it’s often easier not to login at all, but get right

down to fetching data. When Login is not explicitly called, DevForce still does an implicit login, without

credentials, and by default allows the user to login as a guest. Since guest access is rarely wanted in a

production application, you can turn this facility off to ensure that the Entity Server does not allow login

without credentials. You do this by setting a flag called allowAnonymousAccess in the IdeaBlade

Configuration. When set to false, an exception will be thrown if a user attempts to login without

supplying credentials.

XML

<objectServer>

<serverSettings allowAnonymousLogin="false" />

</objectServer>

Authorization In a truly distributed environment, where business logic can operate in an insecure client side

environment, it is critical to guard against a compromised client so that it cannot damage data or

perform restricted operations.

Page 421: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

421 | P a g e

The DevForce platform provides several such mechanisms that operate on the server-side.

Server side role-based security DevForce supports both declarative and programmatic role-based authorization of server-side methods.

Most server-side interface implementations, such as the EntityServerQueryInterceptor,

EntityServerSaveInterceptor , and remotely-invoked methods called via

EntityManager.InvokeServerMethod(), receive an IPrincipal in their input arguments. This IPrincipal is

the one returned from the IEntityLoginManager.Login() call, and represents the current user making the

request. When not explicitly passed into the method or property, the

System.Threading.Thread.CurrentPrincipal can also be retrieved; it, too, represents the current logged-in

user making the request.

In a Silverlight or ASP.NET application, the HttpContext.Current.User can also be used if the

aspNetCompatibilityEnabled flag has been set.

Programmatic Authorization

In any server-side method or property to which access must be restricted or authorized, you can add

some simple code to check the user’s identity. If the user is not authorized for the action, then you can

cancel it (if fetching or saving) or throw an exception.

For example, here’s a simple implementation of EntityServerQueryInterceptor which programmatically

checks the user’s privileges:

C#

public class EntityServerQueryManager : EntityServerQueryInterceptor {

protected override bool AuthorizeQuery() {

return Principal.IsInRole("admin");

}

}

Authorization Attributes

Declarative authorization can also be used to decorate server-side methods with authorization

attributes. DevForce will perform an authorization check for any EntityServerQueryInterceptor or

EntityServerSaveInterceptor implementation, any remotely-invoked methods called via the

EntityManager.InvokeServerMethod, and any query methods used with POCO types. The

RequiresAuthentication and RequiresRoles attributes, or any custom attribute derived from these (or

from the AuthorizationAttribute), will be recognized and used to authorize the current logged-in user

before the method is invoked.

Note that these attributes may not be used with properties.

Here’s a class functionality identical to the one shown above, but coded using declarative security:

Page 422: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

422 | P a g e

C#

[RequiresRoles("admin")]

public class EntityServerQueryManager : EntityServerQueryInterceptor {

}

Note that, in the cases above, you may not cancel the action when using declarative security; and that

more complex logic (such as inspecting the element type of the query or the objects to be saved) cannot

be performed. A PersistenceSecurityException is thrown by DevForce if the user is not authorized for the

action.

Query and Save Interception In addition to the more general authorization attributes described above, you can also intercept both

query and save processing with much finer-grained control via the EntityServerQueryInterceptor and

EntityServerSaveInterceptor classes, and the ClientCanQuery and ClientCanSave attributes.

EntityServerQueryInterceptor and EntityServerSaveInterceptor

In order to intercept the server side operations of querying for or saving entities one or both of these

classes may be subclassed. While these two classes perform very different operations they are similar

enough to merit speaking about both of them together before discussing the individual behaviors of

each class.

Each class contains a number of template methods that can be overridden to modify the base behavior

of the class. A new instance of each class will be created for each query or save performed by the

server. This eliminates the need for developers customizing these classes to be concerned with

threading issues so long as no static variables are used within the customized class. Only a single

subclass implementation of each is permitted, and if none is discovered the base implementation is used

in its place. This also means that when subclassing either of these classes there are no required

overrides that need to be performed; every template method has a working default implementation. It

is suggested, however, that when subclassing a base implementation that a call to the base

implementation is usually a good idea even if the current base implementation is empty.

Most of the template methods provided by these classes have no parameters. This is because all of the

relevant data for each operation is provided by properties and methods on each instance of the class.

This also allows IdeaBlade to extend these base classes in the future without breaking custom developer

code. Many of the template methods described below return a boolean result with a base

implementation that returns ‘true’. A return value of ‘false’ indicates that the operation should be

‘cancelled’. Both querying and saving results include a flag to indicate a ‘cancelled’ operation. Note that

the base implementation of the authorization methods on each class do not return ‘false’ but actually

throw exceptions for an unauthorized query; the idea being that these are actually exceptions and not

‘cancellations’.

Page 423: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

423 | P a g e

EntityServerQueryInterceptor

Virtual methods listed in the order that they will be called -

protected virtual bool AuthorizeQuery()

Determines whether the query can be executed. The base implementation call performs an

analysis of the query expression tree by calling the ‘ClientCanQuery’ method defined below for

each entity type used in the query to determine if any unauthorized types are being accessed.

Note that this will not detect the case where a polymorphic query is executed against a base

type and one of its subtypes has a ‘ClientCanQuery’ restriction. In this case, the

ShouldAuthorizedQueryResult property should be set to ‘true’ and the AuthorizeQueryResult

method may be implemented. It is not usually necessary to override the AuthorizeQueryResult

method; simply returning ‘true’ from ShouldAuthorizeQueryResult is usually sufficient.

protected virtual bool FilterQuery()

Provides a point at which the ‘Query’ property can be modified before the query is executed.

Optionally, the QueryFilters property can be utilized to add filters that will be applied when the

query is actually executed, without actually modifying the ‘Query’ property itself.

protected virtual bool ExecuteQuery()

Performs the actual execution of the query. The query itself may be modified before calling the

base implementation of this method and logging or other post processing operations may be

performed after the base implementation is called. Note that the base implementation must be

called in order for the query to be executed. It is during the call to the base implementation that

any ‘QueryFilters’ defined earlier will be applied.

protected virtual bool AuthorizeQueryResult()

This method is only called if the ‘ShouldAuthorizedQueryResult’ property returns ‘true’. This

method’s base implementation actually walks through all of the entity types to be returned and

throws an exception if the ‘ClientCanQuery’ method below returns false for any of the specified

types.

Other virtual properties and methods -

protected virtual bool DefaultAuthorization { get; }

This property defines the ‘default’ authorization behavior for any types that do not have a

‘ClientCanQuery’ attribute defined. The base implementation returns ‘true’ which means that

by default any type not otherwise restricted is queryable. By returning a ‘false’ here, any types

that are not specifically marked as queryable will restricted.

protected virtual bool ClientCanQuery(Type type)

Page 424: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

424 | P a g e

This method is called from the base implementation of both the ‘AuthorizeQuery’ as well as the

‘AuthorizeQueryResult’ methods. It may be overridden to add additional restrictions or to relax

existing ones. If adding restrictions, make sure that the base implementation is called.

EntityServerSaveInterceptor

Virtual methods listed in the order that they will be called -

protected virtual bool AuthorizeSave()

Determines whether the save can be executed. The base implementation walks all of the types

involved in the save and calls the ‘ClientCanSave’ method defined below for each to determine if

any unauthorized types are being accessed. An EntityServerException will be thrown with a

PersistenceFailure value of ‘Authorization’ if any unauthorized types are encountered.

protected virtual bool ValidateSave()

May be used to extend or suppress validation. The base implementation of this method will call

all instance verifiers registered for each entity being saved. If any verification fails, an

EntityServerException will be thrown with a PersistenceFailure type of ‘Validation’. The

‘VerifierEngine’ property is available in order to discover what validations will be performed.

protected virtual bool ExecuteSave()

Performs the actual execution of the save. The entities involved in the save may be modified

before calling the base implementation of this method and logging or other post processing

operations may be performed after the base implementation is called. Note that the base

implementation must be called in order for the save to be executed. The EntityManager

property defined below contains all of the entities to be saved and its contents may be modified

at any time prior to the base implemention of the ‘ExecuteSave’ method being called.

Other virtual properties and methods

protected virtual bool DefaultAuthorization { get; }

This property defines the ‘default’ authorization behavior for any types that do not have a

‘ClientCanSave’ attribute defined. The base implementation returns ‘true’ which means that by

default any type not otherwise restricted is saveable. By returning a ‘false’ here, any types that

are not specifically marked as saveable will restricted.

protected virtual bool ClientCanSave(Type type)

Page 425: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

425 | P a g e

This method is called from the base implementation of the ‘AuthorizeSave’. It may be

overridden to add additional restrictions or to relax existing ones. If adding restrictions, make

sure that the base implementation is called.

Encryption Security against eavesdropping is provided by encasing all communications using a secure protocol such

as HTTPS / SSL. If more security is required, communications can be double-encrypted using a high-

grade system such as AES or Triple-DES.

ASP.NET Security Integration You can use ASP.NET security features (Membership, Roles, Profile) in a DevForce application.

Integration with these features is available by default in a Silverlight application, but may also be used in

ASP.NET and WinClient applications.

See http://www.asp.net/learn/security/ for information on ASP.NET security features.

Setup

The useAspNetSecurityServices flag in ideablade.configuration controls whether DevForce will use

ASP.NET security features. This flag is applicable to server-side operations only, and will generally be set

in the web.config file.110 When enabled, DevForce will use the AspNetAuthenticatingLoginManager to

handle login processing from clients.

XML

<objectServer>

<serverSettings useAspNetSecurityServices="true" />

</objectServer>

You must also enable AspNetCompatibility for the DevForce services in order to integrate with ASP.NET

services. You set this in the system.serviceModel configuration section. Here's the relevant element in

the system.serviceModel section:

XML

<system.serviceModel>

<!-- Set this to true to allow the BOS to function within the ASP.NET HTTP pipeline and

use ASP.NET services. -->

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

</system.serviceModel>

110 You would, of course, use a standard app.config if using ASP.NET integration in a WinClient application.

Page 426: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

426 | P a g e

You must enable the ASP.NET services you wish to use in the system.web configuration section of the

config file, as well as choose the type of authentication wanted. These steps are described below.

Authentication

Authentication (validating the user’s identity) can take either of two flavors – Forms or Windows.

Forms Authentication

To use Forms authentication, do the following:

1. Set authentication mode="Forms" in the config file. The ASP.NET membership service is enabled by

default.

XML

<system.web>

<authentication mode="Forms" />

</system.web>

2. Pass login credentials in the Login (LoginAsync) call:

C#

var mgr = DomainModelEntityManager.DefaultManager;

LoginCredentials cred = new LoginCredentials("user", "pwd", "domain");

mgr.LoginAsync(cred, LoginCallback, null);

//-- or --

mgr.Login(cred);

(Note the DomainModelEntityManager.DefaultManager is used as an example only. The Login call can

be made on any EntityManager instance.)

DevForce will process this login and validate the credentials with the ASP.NET membership provider. If

the user is authenticated, a FormsAuthenticationTicket is issued. If you want the ticket to be persistent

you should pass a FormsAuthenticationLoginCredential in the Login() call, since this credential allows you

to set the persistence flag.

Windows Authentication

To use Windows authentication, do the following:

1. Set authentication mode="Windows" in the config file. Note that Windows authentication is best suited

to applications running within an organization's intranet.

XML <system.web>

<authentication mode="Windows" />

</system.web>

2. You still need to perform a Login (LoginAsync) call, but you should not pass login credentials. (DevForce

uses the SessionBundle returned from a Login as a token with all subsequent messages. In WinClient

applications, DevForce will perform an implicit Login with null credentials if you do not explicitly call

Page 427: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

427 | P a g e

Login. In Silverlight applications, DevForce will not perform this implicit login processing, and you must

always call LoginAsync.)

C# var mgr = DomainModelEntityManager.DefaultManager;

mgr.LoginAsync(null, LoginCallback, null);

// -- or --

mgr.Login(cred);

When Windows authentication is used, ASP.NET sets the HttpContext.Current.user to a

WindowsPrincipal representing the user. DevForce will use this user as the logged in user and create a

UserBase from the information provided.

For either type of authentication, after Login completes a UserBase instance representing the user is

available on both client and server.

On the client, access this using the Principal property on the EntityManager:

C#

var mgr = DomainModelEntityManager.DefaultManager;

UserBase currentUser = mgr.Principal;

(Note the DomainModelEntityManager.DefaultManager is used as an example only. The Principal is

available from whatever EntityManager performed the Login.)

On the server, the UserBase will be passed into any method which takes an IPrincipal parameter. The

Thread.CurrentPrincipal will also return the UserBase.

Roles

You must enable the Role service in the configuration file in order to use this feature:

XML

<system.web>

<!-- Set to enable/disable the ASP.NET Role Manager. -->

<roleManager enabled="true" />

</system.web>

With roles enabled, user role information will be obtained from the ASP.NET RoleProvider, and role-

based authorization can be used in your application. Use UserBase.Roles to retrieve all roles for the

user, and UserBase.IsInRole() to determine role membership.

Check the ASP.NET documentation for information on how to create and manage roles and assign users

to roles.

Page 428: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

428 | P a g e

Profile

You must enable the Profile service in the configuration file in order to use this feature:

XML

<system.web>

<!-- Set to enable and configure the ASP.NET Profile. These are sample

properties. -->

<profile enabled="true">

<properties>

<add name="WindowSeat" type="bool" defaultValue="false" />

<add name="Building" type="string" defaultValue="A" />

</properties>

</profile>

</system.web>

You also need to extend the UserBase class with the custom properties from your profile. DevForce will

automatically populate these properties from the Profile if the property name and type match, and the

setter is public. Your custom UserBase class must be serializable, since it will be transmitted between

client and server tiers. A sample application using the Profile service is available with the Learning

Resources installed with DevForce.

Here’s a sample class based on the profile properties used above:

C#

[DataContract]

public class CustomUser : UserBase {

public CustomUser(IIdentity identity, IEnumerable<string> roles) :

base(identity, roles) { }

// Custom properties to be loaded from Profile.

[DataMember]

public bool WindowSeat { get; set; }

[DataMember]

public string Building { get; set; }

}

Customizations

Credentials (Forms authentication)

You can pass custom credentials, derived from ILoginCredential, LoginCredential, or

FormsAuthenticationLoginCredential with the Login (LoginAsync) call. With custom credentials, you will

generally also want to provide a custom IEntityLoginManager implementation to receive these

credentials. If you wish to take advantage of existing DevForce ASP.NET service integration, you should

derive your class from the AspNetAuthenticatingLoginManager and override methods as needed.

IEntityLoginManager

You can implement your own IEntityLoginManager or extend the AspNetAuthenticatingLoginManager

to provide custom logic. Any custom implementation will be used if found.

Page 429: Dev Force 2010 Developers Guide

IdeaBlade DevForce Security

429 | P a g e

UserBase

You can also extend the UserBase class. If you enable the ASP.NET Profile service you will want to use a

custom UserBase which contains additional properties retrieved from the profile. DevForce will

automatically return your custom UserBase (if found) without the need to implement a custom

AspNetAuthenticatingLoginManager.

Special situations

If you are already using ASP.NET security features in your application and want DevForce to recognize

the current user identity, call Login (LoginAsync) with null credentials. DevForce will use the

HttpContext.Current.User as its logged in user also. Be sure to perform the setup tasks listed above so

that DevForce can integrate with ASP.NET services.

Troubleshooting

"Error using ASP.NET Membership: Unable to connect to SQL Server database." Message received on a Login

(LoginAsync) call.

This will occur if the ASP.NET membership database cannot be found or opened. You must configure the

ASP.NET membership provider if you wish to use ASP.NET security features, and by default the

AspNetSqlProvider is used. This will use the LocalSqlServer connection string from either your web.config or

the machine.config. The default connection expects a SQLExpress database named aspnetdb.mdf. For more

information on configuring ASP.NET membership see the membership tutorials at

http://www.asp.net/learn/security/ .

The default in the machine.config:

XML

<connectionStrings>

<add name="LocalSqlServer" connectionString="data

source=.\SQLEXPRESS;Integrated

Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User

Instance=true" providerName="System.Data.SqlClient"/>

</connectionStrings>

To override in your web.config (to the default instance of the local SQL Server):

XML

<connectionStrings>

<remove name="LocalSqlServer" />

<add name="LocalSqlServer" connectionString="Data

Source=.;Initial Catalog=aspnetdb;Integrated Security=True;"

providerName="System.Data.SqlClient" />

</connectionStrings>

Page 430: Dev Force 2010 Developers Guide

430 | P a g e

Deployment

Deployment ................................................................................................................................. 430

State of the Release Candidate Documentation ................................................................................ 430

Document Overview ............................................................................................................................ 430

DevForce And the App.Config File .................................................................................................... 431 Creating and Editing a Configuration File ......................................................................................................... 431 IdeaBlade DevForce Configuration Editor ........................................................................................................ 432 DevForce Elements in App.Config .................................................................................................................... 433 Configuration File Location .............................................................................................................................. 434 Client and Server Versions of App.Config ........................................................................................................ 435 Probing in DevForce .......................................................................................................................................... 436

EntityServerQueryInterceptor ............................................................................................... 438

Server ......................................................................................................................................... 438

Data Server Deployment ..................................................................................................................... 439

Deploying a DevForce Silverlight Application .................................................................................. 439 Deploying to IIS Version 6 ................................................................................................................................ 439 Deploying to IIS Version 7 ................................................................................................................................ 443 Troubleshooting ................................................................................................................................................. 447 Resources ........................................................................................................................................................... 448

Deploying a DevForce WinClient Application.................................................................................. 448 Overview ........................................................................................................................................................... 448 Deploying a Single-Tier WinClient Application ............................................................................................... 450 Deploying Two-Tier (Client-Server) WinClient Applications .......................................................................... 450 Deploying N-Tier (Smart-Client) Applications ................................................................................................. 450 Building Blocks ................................................................................................................................................. 451

State of the Release Candidate Documentation

We are working hard to update all of our documentation from DevForce 2009 to DevForce 2010, .NET 4,

and Silverlight 4. During this conversion, you may find some sections that are out of date, but you should

be able to get many of the examples to work, with small modifications, by checking against the API

Documentation for the current method signatures.

Document Overview

This document details deployment processes and piece parts. We begin with a discussion of the

app.config and web.config configuration files; briefly discuss data server deployment; and then provide

Page 431: Dev Force 2010 Developers Guide

431 | P a g e

stepwise instructions for DevForce Silverlight applications and the several flavors of DevForce WinClient

application.

All developers should read the sections DevForce And the App.Config File and Data Server Deployment.

Then,

if deploying a Silverlight app, proceed to the section Deploying a Silverlight Application; or,

if deploying a WinClient app, proceed instead to the section Deploying a DevForce WinClient Application.

DevForce And the App.Config File

The application configuration file, app.config, is critical to making your application run properly. The

app.config file contains settings that govern many important aspects of the way a DevForce application

runs. Some settings determine which data sources are reached and how. Some concern aspects of the

application’s deployment.

The application developer enters these parameters in the IdeaBlade section of app.config. This file

can be edited from within the DevForce Configuration Editor, within Visual Studio, or within Notepad or

any XML editor. The DevForce Configuration Editor provides helpful additional structure, such as

presenting elements currently not represented in the file (and therefore being taken at their default

values). It is very helpful if you don’t remember the exact name or format of a particular element. When

editing the file in Visual Studio, Intellisense is available to help with the elements and attributes allowed.

A DevForce-based, data-driven, distributed application requires the setting of numerous parameters.

We need two slightly different versions of this file, one for the client and one for the server.

The client-side version of the file is typically embedded as a resource file in the executable itself. It may

also be deployed loose in the application’s executables directory; but in the case of the client-side

app.config, we strongly recommend against that. It is an invitation to tampering, and there is some

danger that a user might erase it or lose it while shuffling files on his local disk.

On the server side of an n-tier application, the IdeaBlade configuration information will be included in

the web.config if the BOS is deployed in IIS. If you’re using the ServerConsole host provided by

DevForce, then the configuration file is typically a loose file named ServerConsole.exe.config. If you’re

using the ServerService host provided by DevForce to host the BOS in a Windows Service, then the

configuration file is usually a loose file named ServerService.exe.config.

Creating and Editing a Configuration File In our first experience writing a DevForce application, we take little notice of the application

configuration file since a default app.config is added to your project when using one of the DevForce

project templates to create your project or solution. This default version of the file is pre-set with values

that are appropriate for an application running on the developer’s PC without a remote BOS.

Page 432: Dev Force 2010 Developers Guide

432 | P a g e

We developers quickly outgrow this version and soon find ourselves editing the app.config file, setting

the parameters that indicate where to find helper assemblies and how to deploy the application.

IdeaBlade DevForce Configuration Editor App.config is an XML file and, as such, can be edited with Notepad or any XML editor – as you are likely

to do when tweaking a copy on a server where the DevForce software is not installed.

We recommend editing the file with the IdeaBlade Configuration Editor whenever possible. This editor

understands the IdeaBlade Configuration scheme, provides descriptive help in the status area, and can

eliminate many simple errors such as element tag misspelling. You can use the editor to modify an

existing config file or to create a new one.

Page 433: Dev Force 2010 Developers Guide

433 | P a g e

You can use the IdeaBlade Configuration Editor in two different ways:

1. Launch it from the DevForce folder in the Windows Start menu.

2. Configure it to work within Visual Studio. To do this:

a. Select the app.config file, right-click, and select Open With… option.

b. If you do not see ConfigEditor.exe in the list of programs, click the <Add…> button.

c. On the Add Program dialog, click the ellipsis button to browse to a file. Navigate to the IdeaBlade DevForce installation directory (typically C:\Program Files\IdeaBlade DevForce 2010) and select the file ConfigEditor.exe. Give it any “Friendly Name” you wish; e.g., “DevForce Configuration Editor”.

d. Once DevForce Configuration Editor is in the list, double-click it to open the configuration file in that editor.

DevForce Elements in App.Config The IdeaBlade section of App.config contains elements governing a wide array of application

behaviors. Some of the elements relate to persistence management, some to debugging, some to

deployment, etc.

The IdeaBlade section of app.config consists of attributes and elements. Elements are described below, including

discussion of selected attributes:

Element Description

(Root)

Probe Assembly Names Allows the developer to specify

assemblies to be used in the discovery of

custom implementations of DevForce

interfaces. Any assembly names

specified here supplement the default

DevForce discovery options.

Logging Identify where and how to write the

DevForce DebugLog with tags in this

area.

EdmKeys Optional configuration data applicable

to one or more Entity Data Model data

sources. There will may be several

named <edmKey/> group tags if the

application uses more than one Entity

Data Model. DevForce will use

information in the <connectionStrings>

element to discover data source

information. You may specify an

EdmKey to override that discovery.

.

Page 434: Dev Force 2010 Developers Guide

434 | P a g e

ObjectServer Configuration data governing access to

the DevForce Business Object Server

(BOS) in an n-tier deployment.

Contains clientSettings and

serverSettings child elements to specify

configuration specific to either client or

server.

The isDistributed attributed must be set

to “true” when deployed as n-tier.

The default is “false” – meaning 2-tier

or 1-tier –, in which case the other

settings are irrelevant.

Verifiers Under this element you can define

verifiers external to your application

code. See the chapter “Validation

Through Verification” for more

information on verifiers.

NotificationService Use this element to define settings for

the Push notification feature.

Configuration File Location Shortly after application launch, the DevForce framework looks for the configuration file. The file must

be located in one of several places. In search order they are:

The same directory as the application executable, as a loose file.

The assembly manifest of the application executable, as an embedded resource file.

These options are now detailed:

1. Loose in the application executable directory

The directory that holds the “.exe” file is the application executable directory.

Just where that is may not be as obvious as it seems at first. Let’s suppose that we are using

Visual Studio, haven’t changed its default build configuration patterns, and have selected a

particular project as the start-up project, meaning that this project will produce the executable

assembly.

Under such circumstances, C# executables are in a sub-directory of the start-up project – either

\bin\debug (for a debug build) or \bin\release (for a release build) – and VB.NET executables

are in the \bin sub-directory of the start-up project (for both debug and release builds).

Page 435: Dev Force 2010 Developers Guide

435 | P a g e

If you have added an application configuration file to your Visual Studio project with a Build

Action of None, then when you build your project Visual Studio will automatically rename the

app.config file to <executable name>.exe.config, and copy it to the deployment directory.

2. Embedded as a resource in the executable assembly

If DevForce doesn’t find the configuration file in the executable’s directory it looks next for a

resource embedded in the executable with the name “app.config”

Embedding the configuration file inside the assembly executable’s manifest is a practice

common with .NET resource files generally. By carrying it around in the assembly we don’t have

to worry about losing the file or end users from accidentally deleting this necessary resource. It

also deters the mischievous alterations that loose files invite.

There are three small “gotchas” with embedded resources:

They are embedded, which means we have to recompile the application when we change them.

If we pick a different project as our start-up assembly, we have to remember to move the

configuration file to the new start-up project.

Configuration files embedded as resources will not be found by standard .NET config file probing,

therefore non-IdeaBlade sections of the config file will not be found at run time.

You may also short-circuit the search described above by setting either the

IdeaBladeConfig.ConfigFileLocation or IdeaBladeConfig.ConfigFileAssembly static properties

programmatically.

Client and Server Versions of App.Config During development, and for purposes of discussion, it is often convenient to consider only a single

configuration file. A single file will suffice even in a few production environments and in 1- or 2-tier

WinClient application scenarios. However, there are good reasons to have one version on the server

and a slightly different version on the client, especially when deploying more than two tiers.111

The best reason is security. Observe that each edmKey contains a database connection string. A

connection string identifies exactly what database is used, where it resides, and how it can be accessed.

In many cases, the string contains a user id and password of a user with wide-ranging database

permissions; an expression such as “user id=sa; password=…” is quite common.

It is trivial to crack open the IdeaBlade Configuration file and extract this gold, even when it is an

embedded resource.

Warning: Disassembling a .NET application is also easy. Don‟t think for a minute that there is a good way

to hide the connection string anywhere on the client-side of a .NET smart-client application.

There is no good reason for the connection string to be present on the client machine of a DevForce

remoting application – one in which the Business Object Server runs on a central host and Business

Page 436: Dev Force 2010 Developers Guide

436 | P a g e

Objects are “remoted” to the client. The client talks only to the Business Object Server, never the data

source directly.

A client executable needs many of the settings in the IdeaBlade Configuration File but it doesn’t need all

of them and it certainly shouldn’t have a connection string in a production release.

When a setting is used on both the server and the client, the settings usually should be identical. Yet

even here differences may be permissible and useful; logging details, for example, could be quite

different on server and client.

Accordingly, most production deployments will involve different versions of the configuration file on the

server and on the client.

Change Across Development Environments A code assembly may be invariant as it progresses from the developer’s environment to the test,

staging, and production environments. On the other hand, many of its associated application settings

are almost certain to be different in each of those environments.

Consider the database connection string. The developer is likely to have a test database on her own

machine. If developing a WinClient app, she may have only a single configuration file, as yet not split

between server and client versions.

Q/A is likely to split the Winclient configuration file into client and server versions. It will have a different

test database for the build, one with its own case data, and this means a change to the connection

string. It will have another database (another string) in the staging environment for one final check-out.

There may even be a beta environment with a beta database. When the release is finally deployed, the

assemblies will use another connection string to point to the production database.

In DevForce development it is expected that there will be different configuration files for each

environment and that these files will be managed appropriately in accordance with shop standards and

practices.

The DevForce N-Tier Configuration Starter (available on the Windows Start menu for IdeaBlade

DevForce, in the Tools section) performs an initial split on our behalf, for WinClient applications. We can

use this as a guide but are likely to split it ourselves as we begin to refine our deployment plans and get

closer to a production release.

Probing in DevForce Triggered by different types of requests made by your code, explicitly or otherwise, DevForce searches

selected assemblies for types that satisfy certain criteria. The types of items searched for fall into three

categories:

Classes that implement particular DevForce interfaces

Classes and members that you have decorated with a DevForce-defined attribute

Page 437: Dev Force 2010 Developers Guide

437 | P a g e

Classes that extend particular DevForce base types.

We‟ll look at these individually in a moment. But first we want to make a few general comments about probing.

In WinClient and ASP.NET applications, DevForce will automatically probe all the non-system and non-

IdeaBlade assemblies in the executable/bin folder. In Silverlight applications DevForce will automatically

probe all non-system and non-IdeaBlade assemblies in the application’s XAP file.

Probed DevForce Interfaces The following table lists DevForce interfaces for implementations of which DevForce probes under

appropriate conditions.

Interface

Client- or Server-Side Probing

IConcurrencyStrategy Server

IDataSourceKeyResolver Client and Server

IEntityLoginManager Server

IIdGenerator Client and Server

IKnownType Client and Server

IKnownTypeProvider Client and Server

ITraceLoggerProvider Client and Server

IVerifierProvider Client and Server

Probed DevForce Attributes

Attribute

Client- or Server-Side Probing

EnableClientAccessAttribute Server

DiscoverableTypeModeAttribute - KnownType Client and Server

DevForce Attributes Discovered Using Reflection DevForce uses this attribute as confirmation that a method specified in a client-side

InvokeServerMethod call may be executed.

Page 438: Dev Force 2010 Developers Guide

438 | P a g e

Attribute

AllowRpcAttribute

Probed DevForce Base Types

Base Type

Client- or Server-Side Probing

EntityServerQueryInterceptor Server

EntityServerSaveInterceptor Server

EntityServiceApplication Server

ServiceHostEvents Server

ServiceProxyEvents Client

Developer-Implementable DevForce Interfaces That Aren‟t Probed

Interface Name

ILoginCredential

What To Do If Your Implementation Isn‟t Found If your assembly isn’t found by the default DevForce probing you can programmatically change the

search options. You can also supplement this probing by adding the name of the assembly to the

<probeAssemblyNames> element in the IdeaBlade Configuration.

What Is An Assembly Name? Must It Be Fully Qualified? In non-Silverlight applications, the name DevForce is looking for is the assembly simple name;

i.e., the assembly file name less the “.DLL” or “.EXE”extension. For example, for the DomainModel.dll

assembly, DevForce expects “DomainModel”.

Page 439: Dev Force 2010 Developers Guide

439 | P a g e

In Silverlight applications, the assembly display name should be used in the probe assembly

lists. This display name should include the assembly simple name (defined just above), version,

culture and public key token.

For example, the assembly display name for the DomainModel.dll assembly might look like the

following:

DomainModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

(The public key will be non-null if you have signed the DomainModel assembly.) The version

number is the assembly version defined in your code.

Data Server Deployment

The data server must be accessible from the Application Server or WinClient application, and run a

supported data source.

Relational Database Management (RDBM) systems are the most common enterprise application data

source. DevForce supports all data sources supported by the Microsoft Entity Framework.

You should know your RDBM system well. You are responsible for the correctness of database

connection strings and proper setting of the database configuration options that control performance

and special behaviors (e.g., transaction isolation levels).

The DevForce Business Object Server (BOS) connects to each database, via the Entity Framework, with a

single “user id”. You must grant that user the permissions it needs to perform the operations your

application requires.

In an n-tier deployment (including all Silverlight deployments), the client application never connects

directly to the data source; its access is always mediated through the BOS. We need not and should not

expose the connection string on the client.

Deploying a DevForce Silverlight Application

Select the appropriate section below based on the version of Internet Information Services running on

your target web server:

Deploying to IIS Version 6

Deploying to IIS Version 7

Deploying to IIS Version 6

Page 440: Dev Force 2010 Developers Guide

440 | P a g e

1. Ensure that the XAP mime type is registered in IIS. The server must recognize the .XAP extension in order to serve a Silverlight application. See http://learn.iis.net/page.aspx/262/silverlight/ for more information on registering the mime type for different IIS versions.

2. The DevForce BOS runs as a WCF service, so you must also ensure that WCF is registered with IIS. See the topic, “Ensure That IIS and WCF Are Correctly Installed and Registered” in the document at this URL: http://msdn.microsoft.com/en-us/library/aa751792.aspx. Note that you do not need to create service files or configure endpoints – these are provided for you in the .svc files and in the DevForce-generated web.config (or the samples provided).

3. On the web server, create a physical directory for your application under the web site root folder (typically c:\ inetpub\wwwroot). (The name used for the application in its URL can be different from this folder name.)

4. Create bin, ClientBin, and log folders under the above directory. Change the NTFS permissions on the log folder to give the Users group Modify permission. (This will allow the debug log to be written into this folder.)

5. Create a virtual directory (or choose an existing one) in IIS. You can do this using the Internet Information Services Manager. Give the virtual directory Read and Run scripts permissions.

The web site will normally host both the Silverlight application (the .html, .aspx, and .xap files)

and the DevForce BOS. (Hosting the BOS at a different site as the Silverlight application requires

additional setup steps not covered here, including the provision of a cross-domain policy file.)

6. While still in the Internet Information Services Manager, browse to the application’s log folder, right-click it, select Properties, and remove Read access to the folder. This will prevent non-authorized persons from examining the application’s debug log.

7. Make any necessary changes to the app.config file in your Silverlight project. For example, we might change the objectServer section from this (appropriate for development on a single machine:

XML

<objectServer

remoteBaseURL="http://localhost "

serverPort="9009"

serviceName=" EntityService.svc"

/>

Page 441: Dev Force 2010 Developers Guide

441 | P a g e

to this:

XML

<objectServer

remoteBaseURL="http://www.ideablade.com"

serverPort="80"

serviceName="SilverlightConsole/EntityService.svc"

/>

8. If deploying to production, turn off any diagnostics or debugging settings in your web.config. Specifically,

Make sure the debug attribute of the <compilation> element in the system.web section is set to

false.

XML

<compilation debug=”false”>

Page 442: Dev Force 2010 Developers Guide

442 | P a g e

Deactivate WCF tracing if you have previously activated it.

9. If your deployed app will use a database instance different from the one you used during development, change either the edmKey or connectionStrings element in the web.config to point to the new database. Also make sure that the security model defined in that edmKey is the appropriate one. For example, you may have been using integrated security during development, which might provide, through the deployed application, excessively liberal access to the database server.112 You would want to replace this with database-based security (e.g., SQL Server Logins).

10. Rebuild the solution to create fresh assemblies and a fresh .XAP file.

11. Copy any .aspx files, the web.config, Global.asax, EntityService.svc, EntityServer.svc, and Silverlight.js files to the application folder.

12. Copy all assemblies from the bin folder of your development solution to the bin folder. These should include the following assemblies, which are required by DevForce:

IdeaBlade.Core

IdeaBlade.EntityModel

IdeaBlade.EntityModel.Edm

IdeaBlade.EntityModel.Server

IdeaBlade.Linq

IdeaBlade.Validation

They should also include the assemblies from your own projects (e.g., the assembly produced from your

Silverlight project).

Note that if you subsequently modify the .aspx or Global.asax files, you must force IIS to recompile your

web application. You do this either of the following ways:

a. Rename a DLL in the bin folder, then change its name back to its original name; or

b. Modify the web.config file (e.g., add a dummy comment) and save it.

112 The deployed application will run in an IIS application pool associated with a particular Windows user. Thus, any user of

your application will have the access privileges of that Windows user. You may not want them all to have that level of

access.

Page 443: Dev Force 2010 Developers Guide

443 | P a g e

13. Copy the XAP file from your solution’s ClientBin folder to the ClientBin folder of the application.

14. You are now ready to test your application. Open a browser on a client machine and enter the URL for your application. This will be:

the domain name for your web server, followed by

a forward slash and the alias of the virtual directory you created, followed by

the startup file for your application.

For example,

http://www.ideablade.com/MyApplication/default.aspx

The name of the startup file can be omitted if it has the name of the default startup file for the

virtual directory, e.g.,

http://www.ideablade.com/MyApplication

Deploying to IIS Version 7

1. Ensure that the XAP mime type is registered in IIS. The server must recognize the .XAP extension in order to serve a Silverlight application. See http://learn.iis.net/page.aspx/262/silverlight/ for more information on registering the mime type for different IIS versions.

2. The DevForce BOS runs as a WCF service, so you must also ensure that WCF is registered with IIS. See the topic, “Ensure That IIS and WCF Are Correctly Installed and Registered” in the document at this URL: http://msdn.microsoft.com/en-us/library/aa751792.aspx. Note that you do not need to create service files or configure endpoints – these are provided for you in the .svc files and in the DevForce-generated web.config (or the samples provided).

3. On the web server, create a physical directory for your application under the web site root folder (typically c:\ inetpub\wwwroot). (The name used for the application in its URL can be different from this folder name.)

4. Create bin, ClientBin, and log folders under the above directory. Change the NTFS permissions on the log folder to give the Users group Modify permission. (This will allow the debug log to be written into this folder.)

Page 444: Dev Force 2010 Developers Guide

444 | P a g e

5. Create an application (or choose an existing one) in IIS. You can do this using the Internet Information Services Manager.

To use the physical folder name as the alias... To use an alias that is different from the name of the

physical folder...

The web site will normally host both the Silverlight application (the .html, .aspx, and .xap files)

and the DevForce BOS. (Hosting the BOS at a different site as the Silverlight application requires

additional setup steps not covered here, including the provision of cross-domain policy.)

Page 445: Dev Force 2010 Developers Guide

445 | P a g e

6. While still in the Internet Information Services Manager, browse to and select the application’s log folder. Click the Authentication icon under the IIS section, and disable Anonymous Authentication. This will prevent non-authorized persons from examining the application’s debug log.

7. Make any necessary changes to the app.config file in your Silverlight project. For example, we might change the objectServer section from this (appropriate for development on a single machine:

XML

<objectServer

remoteBaseURL="http://localhost "

serverPort="9009"

serviceName=" EntityService.svc"

/>

to this:

XML

<objectServer

remoteBaseURL="http://www.ideablade.com"

serverPort="80"

serviceName="SilverlightConsole/EntityService.svc"

/>

Page 446: Dev Force 2010 Developers Guide

446 | P a g e

8. If deploying to production, turn off any diagnostics or debugging settings in your web.config. Specifically,

Make sure the debug attribute of the <compilation> element in the system.web section is set to

false.

XML

<compilation debug=”false”>

Deactivate WCF tracing if you have previously activated it.

9. If your deployed app will use a database instance different from the one you used during development, change either the edmKey or connectionStrings element in the web.config to point to the new database. Also make sure that the security model defined in that edmKey is the appropriate one. For example, you may have been using integrated security during development, which might provide, through the deployed application, excessively liberal access to the database server.113 You would want to replace this with database-based security (e.g., SQL Server Logins).

10. Rebuild the solution to create fresh assemblies and a fresh .XAP file.

11. Copy any .aspx files, the web.config, Global.asax, EntityService.svc, EntityServer.svc, and Silverlight.js files to the application folder.

12. Copy all assemblies from the bin folder of your development solution to the bin folder. These should include the following assemblies, which are required by DevForce:

IdeaBlade.Core

IdeaBlade.EntityModel

IdeaBlade.EntityModel.Edm

IdeaBlade.EntityModel.Server

IdeaBlade.Linq

IdeaBlade.Validation

They should also include the assemblies from your own projects (e.g., the assembly produced from your

Silverlight project).

Note that if you subsequently modify the .aspx or Global.asax files, you must force IIS to recompile your

web application. You do this either of the following ways:

c. Rename a DLL in the bin folder, then change its name back to its original name; or

113 The deployed application will run in an IIS application pool associated with a particular Windows user. Thus, any user of

your application will have the access privileges of that Windows user. You may not want them all to have that level of

access.

Page 447: Dev Force 2010 Developers Guide

447 | P a g e

d. Modify the web.config file (e.g., add a dummy comment) and save it.

13. Copy the XAP file from your solution’s ClientBin folder to the ClientBin folder of the application.

14. You are now ready to test your application. Open a browser on a client machine and enter the URL for your application. This will be:

the domain name for your web server, followed by

a forward slash and the alias of the virtual directory you created, followed by

the startup file for your application.

For example,

http://www.ideablade.com/MyApplication/default.aspx

The name of the startup file can be omitted if it has the name of the default startup file for the

virtual directory, e.g.,

http://www.ideablade.com/MyApplication

Troubleshooting Problem:

Your application was running initially and then crashes after a few minutes with an exception

message such as: “Object reference not set to an instance of an object.. --->

System.NullReferenceException: Object reference not set to an instance of an object”.

Solution:

You may have encountered a problem that occurs when the IIS application pool has recycled.

One of the best ways to insure this does not happen is to create a new application pool that

does not recycle on a time-limited basis, and then assign your application to that pool.

Problem:

Your application had been running and then crashes after you make a change to one or more of

the files in the application directory. The exception includes this message: “Could not load file

or assembly 'App_Web_...” .

Page 448: Dev Force 2010 Developers Guide

448 | P a g e

Solution:

You may have encountered a problem that occurs when files in the application folder no longer

match the compiled version located in the “Temporary ASP.NET Files” folder. You can force a

rebuild of your application by deleting the “bin” folder and then replacing it with a copy, or by

running the “aspnet_compiler.exe” command with the “-c” switch. You can find the command

by first browsing to the folder “%SystemRoot%\Microsoft.NET\Framework\” and then opening

the v2.0.xxxxx subfolder (the numbers after v2.0 can vary) .

Here is an example using the virtual directory name of the application (here, MyApp):

aspnet_compiler –v /MyApp -c

Resources

Check the Windows Event Viewer for error messages from "DevForce Object Server" and "ASP.NET".

Examine the DevForce debug log, named DebugLog.xml and located by default in the log folder.

Deploying a DevForce WinClient Application

Overview We enjoy an amazing productivity advantage building DevForce “smart-client”114 applications: we don’t

have to think about deployment issues until remarkably late in the game.

We may complete initial development without deploying anything. All we need is our development PC

with its private copies of .NET, the DevForce framework, a relational database server, and a test version

of the application data source. We compile and execute on our PC. We may use our Internet connection

to reach a web service. That is about the extent of it.

We don’t have to program in any special way to ready the application for an n-tier deployment. We

should not have to change a line of code to deploy to a load-balanced, multiple-server production

environment, serving global clients from behind a firewall.

Eventually we face deployment. A smart-client deployment distributes functionality between host and

client environments. In DevForce we enable this distribution by copying one group of files to the host,

another group to the client, and by setting values in a configuration file.

Deployment Configurations DevForce WinClient applications can be deployed in the following basic configurations:

114 This term has been used in the industry to denote different things: we refer to an n-tier application deployed across the

internet with a WinClient front end.

Page 449: Dev Force 2010 Developers Guide

449 | P a g e

Name Description Specific Deployment

Task

Comments

Single-Tier Single-Tier, rich-client

application with local data

store.

Local data store

deployment

DevForce Client

deployment

May be shrink-wrapped

consumer software.

Client-Server 2-Tier application with a rich

client connecting to back-end

data server.

Data Server deployment

DevForce Client

deployment

Requires a functioning database

connection for each client;

security, reliability, and

scalability limitations. Easy to

deploy.

Smart-Client 3-Tier, Smart-Client

application. Rich client

connects to a middle-tier

DevForce Business Object

Server, which in turn connects

to a Data Server.

Data Server deployment

DevForce Business

Object Server deployment

DevForce Client

deployment

Provides the best security,

performance, and reliability.

Can support disconnected

operation. Complex deployment.

Deployment Phases Initial Deployment – Most of this chapter concerns the initial deployment of the application in both its

server-side and client-side aspects. The hard work is done once we have the first application version

successfully installed on a server and a client. We will describe different deployment scenarios based on

application/deployment types.

Deployment Test – This is critical to a successful application deployment. It may be necessary to deploy

a complex application in multiple stages and test each deployment stage thoroughly.

Application Update – Updating the application with successive versions, a topic covered later in this

chapter, rests on the foundation of the initial deployment and testing of such deployment.

Initial WinClient Deployment In a Nutshell

DevForce initial deployment, simple or complex, boils down to the following activities:

Update DevForce configuration settings in the app.config file – this is in XML format and is required

for both client and/or server-side DevForce components. See the section DevForce And the App.Config

File for details.

Create server and/or client sets of assemblies and executables. See the section Create Client and Server

File Sets for more information. You will always need to create at least one set (client or server-side) of the

deployable files.

Configure the host environment and deploy the server component. This can be very complicated with

many deployment combinations if you are deploying a fully distributed n-tier enterprise/web application

that employs database server, application server, and/or web server. See the section Business Object Server

Deployment for each server component deployment.

Page 450: Dev Force 2010 Developers Guide

450 | P a g e

Configure the client environment and deploy the client component to match the intended client

deployment model. Installing the client set on the client machine can be as simple as clicking on a web-

page link. This step is unnecessary if you are deploying ASP.NET web application. See the section Client

Deployment.

Test the deployment. This is important particularly for more complicated application deployment. See

the section Deployment Test for more information.

Not all steps are required for all deployment configurations. Study the following sections to identify your

deployment configuration type and see the correct set of tasks to perform.

Deploying a Single-Tier WinClient Application

1. Update the app.config file settings, as detailed in DevForce And the App.Config File. You need only a client-side configuration file with database server etc.

2. Create the client-only file set. See the section Create Client and Server File Sets for more information.

3. Configure and deploy a local data store for the application. Typically, a file base data store is deployed by application.

4. Configure the client environment and deploy client files. See the section Client Deployment.

5. Test Deployment. See the section Deployment Test.

6. Refer to the section Troubleshooting as needed.

Deploying Two-Tier (Client-Server) WinClient Applications

1. Update the app.config file settings, as detailed in DevForce And the App.Config File. You need only a client-side configuration file with database server etc.

2. Create the client-only file set. See the section Create Client and Server File Sets for more information.

3. Configure and deploy a database server for the application. See the sedction Data Server Deployment.

4. Configure the client environment and deploy client files. See the section Client Deployment.

5. Test Deployment. See the section Deployment Test.

6. Refer to the section Troubleshooting as needed.

Deploying N-Tier (Smart-Client) Applications

1. Update the app.config file settings, as detailed in DevForce And the App.Config File. You need both client- and server-side configuration files, set for remoting with database server settings, etc.

2. Create both client and server-side file sets. See the section Create Client and Server File Sets for more information.

Page 451: Dev Force 2010 Developers Guide

451 | P a g e

3. Configure and deploy database server for application. See the sedction Data Server Deployment.

4. Configure and deploy application server. See Business Object Server (BOS) Deployment section.

5. Configure the client environment and deploy client files. See the section Client Deployment.

6. Test Deployment. See the section Deployment Test.

7. Refer to the section Troubleshooting as needed.

A Bit of Advice Regarding N-Tier Deployments

Experience has taught us that the deep weeds are in the host set-up. Leaping directly from the single-user

development PC to the corporate network has been tried … with predictably unpredictable results.

We strongly encourage you to do as we do: take incremental steps on a spiral outward from deployment on

a single machine to deployment on the fully-networked environment. Test each step before advancing to

the next:

1. One-box deployment to your own PC

2. Two-box deployment of the server file set to another PC connected by an intranet.

3. Three-box deployment: test external client reaching the PC server from outside the firewall

4. One-box deployment using a web server, again first on your own PC

5. Two-box deployment using a web server using another PC on the intranet

6. Deploy to the company web server and test client deployment from outside the firewall

7. Deploy to production-ready application server; connect to the data source server(s)

8. Add HTTPS to the solo PC deployment

9. Add HTTPS to the intranet web server deployment

10. Add HTTPS to the extranet web server deployment

The major hurdle tends to be the transition to the web server. Web servers can be challenging to configure

properly. We cover the essentials for an IIS web server deployment in this document.

Building Blocks In this section you will find detail on steps referenced in the deployment recipes provided above.

Create Client and Server File Sets If you are deploying a one- or two-tier application, you should only need to create one set of the client-

side files. You may simply follow the Client-Side File Set in the left column in the table below.

Deploying to more than two tiers, however, requires separation of the application files into two sets, a

client and a server set.

Page 452: Dev Force 2010 Developers Guide

452 | P a g e

While we rarely pay much attention to tier separation when writing our code, there a few places where

we have to consider the actual runtime environment. Some code will run on the server, some on the

client, and some on both.

The decision process is not difficult. The file sets usually align as follows:

Component Client-Side File Set (all deployment types) Server-Side File Set (deployment to

more than two tiers)

Application

executable (EXE)

and related UI

assemblies

MyApp.exe (example)

- not needed -

DevForce Business

Object Server

(BOS)

IdeaBlade.EntityModel.Server.dll 115 IdeaBlade.EntityModel.Server.dll116

ServerConsole.exe (Console App)

ServerService.exe (Win Service)

Web.config and Global.asax (IIS)

Application

business object

assemblies

<DomainModel name>.dll(example)

<DomainModel name>.dll

App.config117

app.config (Client version) app.config (Server version)

DevForce

persistence library

assemblies

IdeaBlade.EntityModel.dll

IdeaBlade.Core.dll

IdeaBlade.Validation.dll

IdeaBlade.Linq.dll

IdeaBlade.EntityModel.dll

IdeaBlade.EntityModel.Edm.dll

IdeaBlade.Core.dll

IdeaBlade.Validation.dll

DevForce

TraceViewer

(optional)

WinTraceViewer.exe WinTraceViewer.exe

Server Deployment

115 We need IdeaBlade.EntityModel.Server.dll on the client only if deploying in single-tier or 2-tier (“client / server”) mode.

Exclude this library from the client if deploying as an n-tier application with the Business Object Server (BOS).

116 We always need IdeaBlade.EntityModel.Server.dll on the Server. We must add the ServerConsole.exe if deploying the BOS

as a console application. We add the ServerService.exe instead if deploying the BOS as a Windows Service. We must include

Global.asax and Web.config files if deploying the BOS as IIS service.

117 Include only if deploying a loose app.config file. Do not include this if the app.config is a resource embedded either in the

executable or in AppHelper.dll.

Page 453: Dev Force 2010 Developers Guide

453 | P a g e

This section addresses deployment of the server-side components needed for a DevForce application.

We address configuration settings for the host environment; DevForce-specific requirements for the

data server; and deployment options for the DevForce Business Object Server.

Note that it is possible to develop a Microsoft Windows Installer (MSI) package to deploy all server

components described here.

Business Object Server (BOS) Deployment The DevForce equivalent of an “application server” consists of its “Business Object Server” executable

and a subset of the application assemblies.

The application server must meet the following requirements:

1. Run a Microsoft operating system: Windows XP Professional, Windows Vista, Windows 2000 Server, Windows 2003 Server, Windows 7, Windows 2008 Server, or some successor MS O/S.

2. Run the Microsoft .NET Framework version 4.0.

3. Have access to the data source server(s).

4. Be visible to client machines on the Internet, an intranet or some network supporting HTTP or TCP.

5. Designate an application server directory that holds the “Business Object Server” assembly, the

DevForce framework assemblies, and other auxiliary assemblies. A directory such as

“C:\MyDevForceApplication” will do.

BOS Deployment Scenarios

Essentially, BOS can be deployed in three basic scenarios:

1. Console Application

2. Windows Service

3. Microsoft IIS Service

Here are some of the differences in characteristics and usages:

Console Application Windows Service Microsoft IIS Service

Benefits Easiest to deploy – easy

to update

Relatively easy to deploy –

restarts BOS when server

reboots

Most difficult to deploy –

uses port 80 – allows SSL

setup

Usage Best use is during

development stage

Great production

deployment - intranet

Great production

deployment – internet &

intranet

Main entry file ServerConsole.exe ServerService.exe Web.config

Business Object

assemblies

<DomainModel name>.dll

Page 454: Dev Force 2010 Developers Guide

454 | P a g e

Console Application Windows Service Microsoft IIS Service

IdeaBlade

assemblies

IdeaBlade.EntityModel.Server.dll

IdeaBlade.EntityModel.dll

IdeaBlade.Core.dll

IdeaBlade.Validation.dll

IdeaBlade.Linq.dll

IdeaBlade.EntityModel.Edm.dll

Optional files *.config *.config global.asax

In order to simplify the documentation of the BOS deployment task, we’ll employ these conventions in

illustrating each of the deployment scenarios:

We will use an embedded app.config resource inside the domain model assembly rather than a loose

app.config file.

We‟ll say that the application name is MyApp.

We‟ll say that the Business Object assembly name is DomainModel.dll.

We‟ll say that the target server name is SERVER2003R2. This server will be assumed also to host the IIS.

We‟ll say that the data server name is SQL2008, and that the SQL user sa has access to its databases.

Deploying As a Console Application

Deploying the BOS as a Console Application is the easiest way in a three-tier environment. It is

particularly useful when your application is still in the development stage. You can easily update your

deployed server files with minimal changes. Most of the time, you will only need to change your

app.config file. The following steps outline how to deploy the BOS as a Console Application.

1. Prepare the server-side file set. In particular, you need to change the IdeaBlade section of the app.config

file: set the target remoteBaseURL. The default server name and port values can use the default settings.

Also, you should not use Integrated Security in the connection string of your edmKeys.

XML

<edmKey

connection="metadata=.\NorthwindIB.csdl|.\NorthwindIB.ssdl|.\NorthwindIB.msl;pr

ovider=System.Data.SqlClient;provider connection string=&quot;Data

Source=Sql2008;Initial Catalog=NorthwindIB;User Id=samiam;Password=guessmet;

MultipleActiveResultSets=True&quot;"

logTraceString="false"

name="NorthwindIB" >

</edmKey>

<objectServer remoteBaseURL="http://SERVER2003R2"

serviceName="EntityService" serverPort="9009"

/>

Page 455: Dev Force 2010 Developers Guide

455 | P a g e

2. Recompile all modules including the client application.

3. Create the server-side file set:

ServerConsole.exe

<DomainModel name>.dll

IdeaBlade.EntityModel.Server.dll

IdeaBlade.EntityModel.dll

IdeaBlade.Linq. dll

IdeaBlade.EntityModel. Edm.dll

IdeaBlade.Core.dll

IdeaBlade.Validation.dll

4. Create a directory on the server.

5. Copy the server-side file set above to that directory.

6. Optionally, configure the server-side copy of app.config file (ServerConsole.exe.config here)

7. Run ServerConsole.exe in the target server directory. It should display something similar to this:

The BOS is now ready to communicate to its remote client applications.

Deploying As a Windows Service

To deploy the BOS as a Windows Service, perform the steps outlined in the previous section, “Deploying As a

Console Application”, with the following changes:

1. Include ServerService.exe instead of ServerConsole.exe in your server-side file set.

2. Instead of running ServerConsole.exe in the target server directory, you must first install ServerService.exe

as a Windows service. Do this as follows:

a. Launch the Visual Studio command prompt from the Windows Start menu at Start / Microsoft

Visual Studio 2010 / Visual Studio Tools / Visual Studio Command Prompt

b. Change to the directory to which you have copied the server-side file set.

c. At the Command Prompt, run the InstallUtil tool as follows:

d. After running InstallUtil, the IdeaBlade DevForce Entity Service will be installed as a Windows

service. You can now start, stop, and configure this service using the Services plug-in in the

Microsoft Management Console (available at Control Panel / Administrative Tools / Services).

When the service is running, the BOS is available.

Page 456: Dev Force 2010 Developers Guide

456 | P a g e

Deploying on Microsoft IIS

DevForce applications can be deployed on Microsoft IIS. Some of the reasons you may want to deploy under IIS

are:

You already have thin-client applications served by IIS.

You typically want the application to use port 80 (which can, however, be changed) on a server hosting IIS.

You want to use Secure Socket Layer (SSL) to encrypt data flowing between the server and client.

Your application sports both a smart-client and a browser-based UI.

Typically, deploying the BOS as a Microsoft IIS service is more difficult than deploying it as a Console Application

or Windows Service. This is because you have to modify many settings in different files.

We recommend that you deploy your DevForce application first as a Console Application to work out all

the communication issues, before attempting to install it as an IIS service.

If you are familiar with creating web sites in IIS, the easiest deployment method may be manually to

create a new virtual directory for the BOS and then copy the needed files.

If unfamiliar with IIS, you can create a web setup project to aid the IIS deployment. In brief, the steps

are:

1. Prepare the server-side file set. Besides changing the web.config file, you may also need to update the

global.asaxfile. You may also need *.svc files for the EntityService and EntityServer services. These files

are included as sample files in the DevForce tutorial “IIS Deployment” installed at

LearningResources\110_Deployment\Samples\300COR_DeploymentWithIIS.

Including the Global.asax is optional – it supports remote viewing of server tracing messages, and

allows you to register a VirtualPathProvider to IIS.

The Web.config file tells IIS how to start the BOS. .

EntityService.svc and EntityServer.svc define the WCF services.

2. Create a virtual directory to host the BOS.

3. Create a bin sub-directory, and place in it all assemblies identified above as necessary.

You will need *.svc (service file) for all services.

You can copy the EntityService.svc file w/o changes.

You will need an EntityServer*.svc file for each data source extension used. See the

EntityServer.svc and EntityServer_Dev.svc samples for guidance.

4. Copy the global.asax file.

By default this file contains startup logic to verify that an IdeaBlade configuration was found and

to enable use of the TraceViewer. You can change this as needed. Any errors during startup will

be written to the Event Viewer, and possibly the debug log (DebugLog.xml) if one could be

created. (Not being able to create a log file is a very common error encountered as you work

through initial deployment issues).

Page 457: Dev Force 2010 Developers Guide

457 | P a g e

5. Decide where your log file will be written.

If a full path to the logFile is not specified, the location defaults to the application directory

holding the web.config file. Placing the log file here is not a good idea, since all users will be

able to browse to the debuglog.xml using their web browser and will therefore see all your

secrets! You should therefore either specify a secure directory for the log file, or turn off xml

file browsing.

We recommend placing the debug log file in a "log" subdirectory from which all IIS permissions

have been removed.

To place the debuglog in a non-default directory, you can specify a full path name, or a relative

path name (such as logFile="log\DebugLog.xml"). Note that you cannot use the ~ character or

virtual paths here.

Don't put the debug log in the "bin" subdirectory, because writes to the it will cause IIS to

recompile all assemblies there.

See the 300COR_DeploymentWithIIS code sample in the Deployment topic folder of the Learning

Resources for more information.

Hosting the BOS on SSL

Secure Socket Layer (SSL) is the industry standard approach to encrypting data transmitted over the

Internet and thus protecting the confidentiality of data in transit.

There is nothing unusual about engaging SSL for a DevForce application and its BOS. The most

troublesome procedure would probably be adding a valid security certificate into the target server. You

must ask your network administrator to ensure it is set up correctly.

Let’s assume we deployed the BOS in IIS as described in the previous section, and that we have put in

place a loose app.config file for the BOS using the steps from that section. We would like to deploy the

DevForce BOS using SSL at port 443.

We would need to do the following:

1. Ensure that the server‟s Firewall (if any) does not block the SSL port – 443.

2. Ensure that a valid security certificate is installed at the target server.

a) Locate and start the Internet Information Services (IIS) Manager at the server.

b) Expand SERVER2003R2(local computer) > Expand Web Sites > Expand Default Web Site.

c) Select and right-click Default Web Site to display its context menu.

d) Select Properties to display the Default Web Site Properties dialog.

e) Select Directory Security tab.

Page 458: Dev Force 2010 Developers Guide

458 | P a g e

f) Click on the View Certificate button to show the Certificate dialog. If this button is

disabled, then no valid certificate was installed in this server. You must stop and ask your

network administrator to obtain a valid security certificate for the target server.

3. Update the IIS to listen on SSL port 443.

4. Again display the Default Web Site Properties dialog.

Select Web Site tab.

Set SSL port value to 443.

Click OK to save.

Now the DevForce client application will communicate to the BOS via SSL at port 443 using the HTTPS

protocol.

Using the TraceViewer Under IIS

You likely will want to enable Trace Viewing when deploying your application on IIS.

Call the TracePublisher.LocalInstance.MakeRemotable() method in the Application_Start method of

the Global.asax.cs file of your IIS application. The TracePublisher class is in IdeaBlade.Core.

// C# protected void Application_Start(Object sender, EventArgs e) { IdeaBlade.Core.TracePublisher.LocalInstance.MakeRemotable(); }

' VB Protected Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs e) IdeaBlade.Core.TracePublisher.LocalInstance.MakeRemotable() End Sub

Client Deployment Most DevForce WinClient customers prefer to use ClickOnce deployment for their client applications.

Parts of this section describe client application deployment with a Microsoft Installer package. These

techniques may still be useful in circumstances where ClickOnce is inappropriate.

Configure the Client Environment

No matter how we deploy to the client, the client must:

run a Windows O/S that supports the Microsoft .NET Framework version 4.0;

have installed Microsoft .NET Framework version 4.0; and

be able to access the application server on the Internet, intranet, or network

Security Permissions

Microsoft .NET offers a wide range of client-side security options covering fine grained control of

operations and of access to local resources such as the file system. There are ways to trust certain

assemblies more than others. It is a rich topic, beyond the scope of this document.

At the extremes are (a) wide-open access such as we have when we are logged-in to Windows with

administrator privileges and (z) the most restrictive security setting in the IE browser.

Page 459: Dev Force 2010 Developers Guide

459 | P a g e

“Permanent” installation in a client PC directory may require more permission than is routinely available

on your users’ PCs. Most applications don’t need unfettered access and it is appropriate to restrict client

boundaries to just what the application requires. There are a variety of ways to set local security options

automatically prior to installing the application. You may wish to consult a DevForce professional to

discuss the pros and cons and discover what is right for you.

We can run a smart-client application inside an IE browser configured with the standard browser

security settings. Application files load into the temporary browser cache instead of a PC directory.

However, many smart-client features are not available in this deployment model. For example, the

browser only supports 100% managed code; whereas many 3rd party UI controls are built with

unmanaged code, and won’t run.

MSI Client Deployment

As on the server, “installation” means “copy the files to a specific location”. To make it easy for the user,

we usually provide a Microsoft Windows Installer (MSI) file to perform setup.

The user first has to get this setup file. She might download it from a web page or copy it from a CD.

Then she executes it, answers a few questions, and the setup utility drops the files into the (user)

designated directory.

There are no hidden files, nothing added to the Window directory, and – most importantly – no

Windows Registry entries! We might get fancy and offer to add a shortcut to the Windows Start Menu

and desktop.

The following steps show you how to create a simple client Setup Project from Visual Studio:

1. Prepare the client-side file set.

2. Create a new Setup Project under your Solution Explorer to include the client-side file set.

a) Right-click your solution entry inside the Solution Explorer, select Add, and then select

New Project.

b) Inside the Add New Project dialog, select Other Project Types > Setup and Deployment

project type.

c) Select Setup Project template on the right pane.

d) Enter the target project name (e.g. MyWinApp) and click OK to create the new project.

3. Add all client-related files and assemblies into the Application Folder of the Setup Project.

a) Select the File System Editor view under the Solution Explorer tool bar.

b) Right-click on the Application Folder, select Add, and select Project Output to display the

Add Project Output Group dialog.

4. Select the Primary output of your application project (e.g. MyApp) and click OK.

Note: this step also brings in other assemblies including DomainModel.dll118

. Optionally, you may

include the app.config file as needed.

5. Rebuild all projects including the new Setup Project – MyWinApp. It should produce two files in its

output folder – Setup.exe and the msi file (e.g., MyWinApp.msi).

Page 460: Dev Force 2010 Developers Guide

460 | P a g e

6. Copy them into the target client machine or a shared server directory.

7. Run Setup.exe to install the client DevForce application. The Setup Wizard will come up – just follow

the on-screen instructions of the wizard to complete.

De-installation is therefore safe and obvious: the client simply deletes the directory and some shortcuts.

InstallShield and MSI have no trouble with these chores.

When we deploy the client application within the browser environment, the browser does the copying

for us, but into its own cache rather than a “permanent” installation directory. There is no explicit de-

installation; clearing the browser cache does that trick.

ClickOnce Client Deployment

This is relatively straightforward if you build your client application using the ClickOnce publish option.

Simply publish your application’s ClickOnce file set into the target host server for users to download and

install.

Here are the steps to publish MyApp from Visual Studio to a shared network directory:

1. Navigate to and select your client application‟s executable project in Solution Explorer. E.g. MyApp.

2. Right-click and select Properties in the context menu to display MyApp properties in the main pane.

3. Select Publish tab.

4. Fill in all necessary publish information and options. For most settings, just use the default values.

5. Set the Publishing Location (E.g. \\Server2003r2\Public\ClickOnce\MyApp\)

6. Set the Publish Version.

7. Click the Application Files button to change all IdeaBlade assemblies to be included for the client

deployment. Inside the Application Files dialog, select each IdeaBlade assembly, and then change the

Publish Status from Prerequisite (Auto) to Include.

8. Click OK to save and return to the Publish tab.

9. You can use the Publish Wizard to walk through the rest of the settings, or simply click Publish Now.

10. Upon completion of the publishing step above, Visual Studio will automatically open up the publishing

directory or site.

Page 461: Dev Force 2010 Developers Guide

461 | P a g e

11. You can then click the Install button to install MyApp from the published site to your local client

machine.

Application Update for ClickOnce

One of the great features of ClickOnce is that it can auto-update your deployed client application. By

default, a ClickOnce-published application will check for newer updates before it starts.

Let’s assume we made some changes to MyApp. Here are the typical next version deployment steps:

Recompile the MyApp project or its solution.

1. Expand and select the MyApp project in Solution Explorer.

2. Right-click and select Properties in the context menu to display the MyApp properties in the main

pane.

3. Select the Publish tab. Note that the Publish Version had already been updated to 1.0.0.1 from the last

ClickOnce publish.

4. Click Publish Now to publish the new version.

5. Upon completion, Visual Studio will open up the publishing directory/site. Note that the Version

number at the site is now 1.0.0.1.

6. Do not install and simply close the install site.

7. Instead go to a client machine that had previously installed version 1.0.0.0 of the application.

8. Run MyApp 1.0.0.0.

9. A dialog will display as shown below to tell you a new version is available for MyApp.

Page 462: Dev Force 2010 Developers Guide

462 | P a g e

10. Simply click OK and MyApp will be updated to the new version 1.0.0.1 and running along.

Other Reading on ClickOnce

There are many options and features in ClickOnce, and you may want to learn more about them. Links

are provided below to help you to do so.

Other Useful Sources on ClickOnce

Title Location

Smart-Client Deployment with

ClickOnce(TM): Deploying Windows

Forms Applications with

ClickOnce(TM). Book by Bryan Noyes,

published Dec.2006 (Microsoft .NET

Development Series)

Introducing Client Application

Deployment with “ClickOnce”, Duncan

Mackenzie

http://msdn.microsoft.com/library/default.asp?url=/library/en-

us/dnwinforms/html/clickoncetrustpub.asp

“ClickOnce for the Real World, not

Hello World”, Julia Lerner

http://www.code-magazine.com/Article.aspx?quickid=0611041

“ClickOnce: Bringing Ease and

Reliability to Smart-Client

Deployment”, Patrick Darragh

http://www.code-magazine.com/Article.aspx?quickid=0601041

“Administering ClickOnce

Deployments”, Bryan Noyes

http://msdn.microsoft.com/library/default.asp?url=/library/en-

us/dnwinforms/html/clickoncetrustpub.asp

Page 463: Dev Force 2010 Developers Guide

463 | P a g e

Title Location

“Signing your ClickOnce Manifest”,

Bryan Noyes

http://msdn.microsoft.com/library/default.asp?url=/library/en-

us/dnwinforms/html/clickoncetrustpub.asp

ClickOnce Limitations

ClickOnce has a number of limitations, among them the following:

Applications are installed for a single user. You cannot accommodate every user of the workstation in a

single install.

Applications are installed in the system-managed folder specific to the user. You cannot change or

influence the choice of the installation folder.

You can provide for exactly one Start Menu short cut for the application. It must appear in the form

[Publisher Name] ► [Product name]. You can‟t change this. You can‟t add supplemental short-cuts

such as one that points to an uninstall, a help file, or a support page on a web site. You can‟t add short-

cuts to any other folder such as StartUp or Favorites.

You can‟t change or enhance the setup wizard with additional dialogs and options.

You can‟t change the how Click Once generates the installation web page. You can modify its HTML

after it has been generated.

You can‟t install DLLs into the Global Assembly Cache (GAC).

The installation cannot perform custom actions such as creating a database, registering file-types for the

Windows Explorer window, or make any changes to the Registry.

You can work around some of these limitations by, for example, having your application perform them

on start-up.

However, you’ll want to switch to an MSI or 3rd party installer if your requirements get even a little

complex. You must then abandon the Click Once auto-update feature.

ClickOnce Deployment with a CAB application

There are some specific issues related to the use of ClickOnce with an application that incorporates the

Composite UI Application Block (CAB) from the Microsoft Patterns and Practices group. If you need to

deploy such an application using ClickOnce, please contact support via

http://www.ideablade.com/CustomerSupportRequestForm.aspx

which can supply you with some relevant third-party documentation.

Deployment Test We found the following thoughts sufficiently important to merit a small sidebar nearer the front of this

chapter, but they are worth repeating here:

Experience has taught us that the deep weeds are in the host set-up. Leaping directly from the single-

user development PC to the corporate network has been tried … with predictably unpredictable results.

Page 464: Dev Force 2010 Developers Guide

464 | P a g e

We strongly encourage you to do as we do: take incremental steps on a spiral outward from

deployment on a single machine to deployment on the fully-networked environment. Test each step

before advancing to the next:

One-box deployment to your own PC

1. Two-box deployment of the server file set to another PC connected by an intranet.

2. Three-box deployment: test external client reaching the PC server from outside the firewall

3. One-box deployment using a web server, again first on your own PC

4. Two-box deployment using a web server using another PC on the intranet

5. Deploy to the company web server and test client deployment from outside the firewall

6. Deploy to production-ready application server; connect to the data source server(s)

7. Add HTTPS to the solo PC deployment

8. Add HTTPS to the intranet web server deployment

9. Add HTTPS to the extranet web server deployment

The major hurdle tends to be the transition to the web server. Web servers can be challenging to

configure properly. We cover the essentials for an IIS web server deployment in this document.

N-Tier Configuration Starter

We developers spend most of our time eking out our programs in the privacy of our own machines. It is

better to test the results on our local machines before sharing our bugs with our colleagues.

Accordingly, when testing an n-tier application it is wise to deploy locally first; and the N-Tier

Configuration Starter is a terrific time-saver in this respect.

The N-Tier Configuration Starter is designed as a training tool, or at best as a first step for real-world n-

tier deployments. It is serviceable for simple deployments on a solo PC but will be quickly superseded by

other methods for wider or more complex deployments.

Page 465: Dev Force 2010 Developers Guide

465 | P a g e

After launching it from the Windows Start Menu: IdeaBlade DevForce 2010 / Tools / N-Tier

Configuration Starter and setting the Source Path to the folder that contains your application’s front-

end project, it looks something like this:

Run the Deployed Application

No matter how deployed, whether on a single machine or in a multi-server distributed environment, the

basic sequence is always the same:

1. Launch ServerConsole.exe on the server(s).

2. Launch the client application.

Run the Server

For example, we might launch something like the following on our solo PC:

C:\zzzDeSvnIt\ComposingWinforms\UI\bin\debug\Server\ServerConsole.exe

The Business Object Server is up and running in a console window. It has found our business object

assembly (or assemblies).

The server can run also as a windows service. It’s a different executable, which must be registered with

windows, but is the same in other respects.

Page 466: Dev Force 2010 Developers Guide

466 | P a g e

Before we move on, it’s fun and instructive to start the Trace Viewer (Start / IdeaBlade DevForce /

Tools / Trace Viewer):

OK, it’s not fun yet. When we run our application, we’ll see evidence of RemoteServer behavior appear

here as it happens.

Run the Client Application

In our example, we launch

C:\zzzDeSvnIt\ComposingWinforms\UI\bin\debug\Client\UI.exe

Our application starts, and we open a tab for editing, which causes the app to retrieve some data:

Page 467: Dev Force 2010 Developers Guide

467 | P a g e

Now the Trace Viewer is a little more revealing. It shows key events and a LINQ-like representation of

the queries submitted against the Entity Framework:

(A configuration option causes the SQL code generated by the Entity Framework for submission against

the back-end database to be logged as well.)

The Trace Viewer is convenient especially for seeing the effects of client actions on the server in real

time. The information displayed in the Trace Viewer (and more) is captured in the server’s XML debug

log located in the server’s directory. The log is called DebugLog.xml by default.

The application writes to XML debug logs on both client and server by default. These are essential

resources for understanding and debugging our applications.

If anything goes wrong, check the server debug logs early in your analysis. Logged information identifies

the source of the IdeaBlade configuration settings, the service configuration, license information, and

activity against the server.

Troubleshooting This section includes the following topics:

MSDN: Specific Errors in ClickOnce Deployments

Errors When Publishing with Visual Studio

Using Mage

Other Errors

MSDN: Specific Errors in ClickOnce Deployments

This topic lists the following common errors that can occur when you deploy a ClickOnce application,

and provides steps to resolve each problem.

When you try to locate an .application file, nothing occurs, or XML renders in Internet Explorer, or you

receive a Run or Save As dialog box

This error is likely caused by content types (also known as MIME types) not being registered

correctly on the server or client.

Page 468: Dev Force 2010 Developers Guide

468 | P a g e

First, make sure that the server is configured to associate the .application extension with content

type "application/x-ms-application".

If the server is configured correctly, ensure that the .NET Framework 2.0 is installed on your

computer. If the .NET Framework 2.0 is installed, and you are still seeing this problem, try

uninstalling and reinstalling the .NET Framework 2.0 to re-register the content type on the client.

Error message says, "Unable to retrieve application. Files missing in deployment" or "Application

download has been interrupted, check for network errors and try again later"

This message indicates that one or more files being referenced by the ClickOnce manifests cannot

be downloaded. The easiest way to debug this error is to try to download the URL that ClickOnce

says it cannot download. Here are some possible causes:

- If the log file says "(403) Forbidden" or "(404) Not found," verify that the Web server is configured so

that it does not block download of this file. For more information, see Server and Client

Configuration Issues in ClickOnce Deployments.

- If the .config file is being blocked by the server, see the section "Download error when you try to

install a ClickOnce application that has a .config file" later in this topic.

- Determine whether this occurred because the deploymentProvider URL in the deployment manifest is

pointing to a different location than the URL used for activation.

- Ensure that all files are present on the server; the ClickOnce log should tell you which file was not

found.

- See whether there are network connectivity issues; you can receive this message if your client

computer went offline during the download.

Download error when you try to install a ClickOnce application that has a .config file

By default, a Visual Basic Windows-based application includes an App.config file. There will be a

problem when a user tries to install from a Web server that uses Windows Server 2003, because

that operating system blocks the installation of .config files for security reasons. To enable the

.config file to be installed, click Use ".deploy" file extension in the Publish Options dialog box.

You also must set the content types (also known as MIME types) appropriately for .application,

.manifest, and .deploy files. For more information, see your Web server documentation.

For more information, see "Windows Server 2003: Locked-Down Content Types" in Server and Client

Configuration Issues in ClickOnce Deployments.

Error message: "Application is improperly formatted;" Log file contains "XML signature is invalid"

Ensure that you updated the manifest file and signed it again. Republish your application by using

Visual Studio or use Mage to sign the application again.

You updated your application on the server, but the client does not download the update

This problem might be solved by completing one of the following tasks:

- Examine the deploymentProvider URL in the deployment manifest. Ensure that you are updating the bits

in the same location that deploymentProvider points to.

- Verify the update interval in the deployment manifest. If this interval is set to a periodic interval, such as

one time every six hours, ClickOnce will not scan for an update until this interval has passed. You can

change the manifest to scan for an update every time that the application starts. Changing the update

Page 469: Dev Force 2010 Developers Guide

469 | P a g e

interval is a convenient option during development time to verify updates are being installed, but it slows

down application activation.

- Try starting the application again on the Start menu. ClickOnce may have detected the update in the

background, but will prompt you to install the bits on the next activation.

During update you receive an error that has the following log entry: "The reference in the deployment

does not match the identity defined in the application manifest"

This error may occur because you have manually edited the deployment and application manifests,

and have caused the description of the identity of an assembly in one manifest to become out of

sync with the other. The identity of an assembly consists of its name, version, culture, and public key

token. Examine the identity descriptions in your manifests, and correct any differences.

First-time activation from local disk or CD-ROM succeeds, but subsequent activation from Start Menu

does not succeed

ClickOnce uses the Deployment Provider URL to receive updates for the application. Verify that the

location that the URL is pointing to is correct.

Error: "Cannot start the application"

This error message usually indicates that there is a problem installing this application into the

ClickOnce store. Either the application has an error or the store is corrupted. The log file might tell

you where the error occurred.

Things to verify:

- that the identity of the deployment manifest, identity of application manifest, and identity of the main

application EXE are all unique.

- that your file paths are not longer than 100 characters. If your application contains file paths that are

too long, you may exceed the limitations on the maximum path you can store. Try shortening the paths

and reinstall.

PrivatePath settings in application config file are not honored

To use PrivatePath (Fusion probing paths), the application must request full trust permission. Try

changing the application manifest to request full trust, and then try again.

During uninstall a message appears saying, "Failed to uninstall application"

This message usually indicates that the application has already been removed or the store is

corrupted. After you click OK, the Add/Remove Program entry will be removed.

During installation, a message appears that says that the platform dependencies are not installed

You are missing a prerequisite in the GAC (global assembly cache) that the application needs in

order to run.

Errors When Publishing with Visual Studio

Publishing in Visual Studio fails

Ensure that you have the right to publish to the server that you are targeting. For example, if you are

logged in to a terminal server computer as an ordinary user, not as an administrator, you probably

will not have the rights required to publish to the local Web server.

Page 470: Dev Force 2010 Developers Guide

470 | P a g e

If you are publishing with a URL, ensure that the destination computer has FrontPage Server

Extensions enabled.

Using Mage

You tried to sign with a certificate in your certificate store and a received blank message box

In the Signing dialog box, you must:

- Select Sign with a stored certificate, and

- Select a certificate from the list; the first certificate is not the default selection.

Clicking the "Don't Sign" button causes an exception

This issue is a known bug. All ClickOnce manifests are required to be signed. Just select one of the

signing options, and then click OK.

Other ClickOnce Errors

The following table shows some common error messages that a client-computer user may receive when the user

installs a ClickOnce application. Each error message is listed next to a description of the most probable cause for the

error.

Error message Description

Application cannot be started. Contact the

application publisher.

Cannot start the application. Contact the

application vendor for assistance.

These are generic error messages that occur when the application cannot be started, and

no other specific reason can be found. Frequently this means that the application is

somehow corrupted, or that the ClickOnce store is corrupted.

Cannot continue. The application is

improperly formatted. Contact the

application publisher for assistance.

Application validation did not succeed.

Unable to continue.

Unable to retrieve application files. Files

corrupt in deployment.

One of the manifest files in the deployment is syntactically not valid, or contains a hash

that cannot be reconciled with the corresponding file. This error may also indicate that

the manifest embedded inside an assembly is corrupted. Re-create your deployment and

recompile your application, or find and fix the errors manually in your manifests.

Cannot retrieve application.

Authentication error.

Application installation did not succeed.

Cannot locate applications files on the

server. Contact the application publisher

or your administrator for assistance.

One or more files in the deployment cannot be downloaded because you do not have

permission to access them. This can be caused by a 403 Forbidden error being returned by

a Web server, which may occur if one of the files in your deployment ends with an

extension that makes the Web server treat it as a protected file. Also, a directory that

contains one or more of the application's files might require a username and password in

order to access.

Page 471: Dev Force 2010 Developers Guide

471 | P a g e

Cannot download the application. The

application is missing required files.

Contact the application vendor or your

system administrator for assistance.

One or more of the files listed in the application manifest cannot be found on the server.

Verify that you have uploaded all the deployment's dependent files, and try again.

Application download did not succeed.

Check your network connection, or contact

your system administrator or network

service provider.

ClickOnce cannot establish a network connection to the server. Examine the server's

availability and the state of your network.

An error has occurred writing to the hard

disk. There might be insufficient space

available on the disk. Contact the

application vendor or your system

administrator for assistance.

This may indicate insufficient disk space for storing the application, but it may also

indicate a more general I/O error when you are trying to save the application files to the

drive.

Cannot start the application. There is not

enough available space on the disk.

The hard disk is full. Clear off space and try to run the application again.

Too many deployed activations are

attempting to load at once.

ClickOnce limits the number of different applications that can start at the same time. This

is largely to help protect against malicious attempts to instigate denial-of-service attacks

against the local ClickOnce service; users who try to start the same application repeatedly,

in rapid succession, will only end up with a single instance of the application.

Shortcuts cannot be activated over the

network.

Shortcuts to a ClickOnce application can only be started on the local hard disk. They

cannot be started by opening a URL that points to a shortcut file on a remote server.

The application is too large to run online in

partial trust. Contact the application

vendor or your system administrator for

assistance.

An application that runs in partial trust cannot be larger than half of the size of the online

application quota, which by default is 250

Errors During IIS n-Tier Deployment

This section concentrates on issues that arise while deploying to an IIS n-tier environment.

IIS is notorious for the variety of maddening deployment melt-downs that are traced to subtle

configuration mistakes. IIS messages are rarely helpful. Time to put on your deerstalker cap and

Inverness cape and make like Sherlock Holmes.

First confirm that testasa.aspx runs successfully, as follows:

Open testasa.aspx in your browser. If it opens without error it will show where the IdeaBlade

configuration was loaded from, and the location of the debug log. If the page fails to open, use the error

information displayed to help diagnose the problem.

Here are some common problems:

Page 472: Dev Force 2010 Developers Guide

472 | P a g e

"The type initializer for 'IdeaBlade.Core.TraceFns' threw an exception".

This error occurs at startup when DevForce cannot write to the log file for some reason. Use the

Windows Event Viewer to check for further information on this error. This problem is commonly

due to folder/file permissions, or the folder was not found.

"Unable to connect to http://localhost:80/SampleService/EntityService.svc. The server or internet

connection may be down."

First check the server debug log to see if service startup information was written to the log. If

the log is not present, then check the Windows Event Viewer to see if errors were logged there.

The service may fail to start due to the configuration and log file problems mentioned above,

bad configuration information in the serviceModel section, or a myriad of other reasons. :)

"The underlying provider failed on open."

Check the server debuglog for more information. In IIS, this message is typically received

because the ASP.NET account does not have appropriate privileges to the database. To address

this, either grant the account (often ASP.NET or NETWORK SERVICE) the privileges needed, or

change the login account in the connection string.

"The specified metadata path is not valid."

If you are using "loose" metadata artifact files (*.csdl, *.ssdl, *.msl) they are expected to be in a

<vroot>\bin directory when running IIS, and the SampleService\bin directory when running under IIS. If

you are using "embedded" artifact files, the server model assembly will contain these files as embedded

resources.

The connection string should correctly reflect which type of metadata files you are using. Loose

artifact files will look something like this (only metadata portion of connection string shown):

XML connection="metadata=.\ServerModelNorthwindIB.csdl|.\ServerModelNorthwindIB.ssdl|

.\ServerModelNorthwindIB.msl;

while embedded files will look something like this:

XML connection="metadata=res://ServerModelNorthwindIB;

This concludes our discussion of Deployment. Should you have issues not covered here, please contact

IdeaBlade Customer Support here.

Page 473: Dev Force 2010 Developers Guide

IdeaBlade DevForce Troubleshooting

473 | P a g e

Troubleshooting

Troubleshooting ......................................................................................................................... 473

State of the Release Candidate Documentation ................................................................................................ 473

General Troubleshooting .................................................................................................................................... 473

Troubleshooting Silverlight Apps ....................................................................................................................... 473

Contacting Support ............................................................................................................................................. 476

Identifying your DevForce version .................................................................................................................... 477

Upgrading Your Software ................................................................................................................................... 478

State of the Release Candidate Documentation

We are working hard to update all of our documentation from DevForce 2009 to DevForce 2010, .NET 4,

and Silverlight 4. During this conversion, you may find some sections that are out of date, but you should

be able to get many of the examples to work, with small modifications, by checking against the API

Documentation for the current method signatures.

General Troubleshooting

10. You see long delays (5 seconds or more) the first time a query is executed and/or the first time a save is

performed.

You may benefit from using pre-generated views of the Entity Model. This is usually most helpful when

the model is large, or when there are many relationships defined, or when some tables contain a large

number of columns and foreign keys.

To use pre-generated views you will need to use the EdmGen.exe tool from Microsoft to generate

“views” for the model. You then include this generated code in your Entity Model project. See the

following MSDN article for complete directions: http://msdn.microsoft.com/en-

us/library/bb896240.aspx.

Troubleshooting Silverlight Apps

11. You attempt to Connect to the BOS from the Silverlight client and receive the exception "An error occurred

while trying to make a request to URI 'http://localhost:9009/EntityService.svc'"

Page 474: Dev Force 2010 Developers Guide

IdeaBlade DevForce Troubleshooting

474 | P a g e

Connection errors can have many causes, but the first thing to check, especially in a new application using the

ASP.NET Development Server, is that the Silverlight application is actually "served" by the web application.

You can see this by looking at the address bar in the browser. If it doesn't start with "http://" then the

application is instead loading from the file system. Why is this a problem? Because, for security reasons, a

Silverlight application cannot make service requests unless served by a web server. In DevForce Silverlight this

means that the application cannot connect to, or make other requests of, the BOS; thus, data cannot be retrieved

from or saved to the back-end data source.

The problem is easily remedied by ensuring that the web application project is always the startup

project in your solution.

12. "*** License violation *** - 'Distributed BOS' not supported with the current license: StandardEF"

You must have a license for DevForce Silverlight in order to develop Silverlight applications with

DevForce. The Silverlight samples in the Learning Units were created with an SL license key and

you'll be able to run the samples as long as you don't regenerate the domain model. Once you

regenerate the model with your license key, the sample may stop working due to the license

violation.

13. I get the following exception when trying to fetch: "Unable to locate type: XX.YY"

This not-so-friendly message may be caused by a type name mismatch between your .NET and

Silverlight domain model assemblies. DevForce will seamlessly transmit entities between the SL and

BOS tiers, but it does this using what is essentially a "shared" domain model. DevForce expects to

see entities having the same fully-qualified type name, for instance "DomainModel.Customer,

DomainModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", in both the .NET and

Silverlight assemblies holding the model.

This is why DevForce attempts to keep the assembly and namespace names in sync between the

two projects, since without this type name equality, entities cannot move between tiers. This

restriction will likely be removed in later releases of DevForce Silverlight. To fix the exception,

ensure that the assembly and namespace names of the two projects containing the domain model

are identical.

14. Why aren't my breakpoints working?

This has nothing to do with DevForce, but we run into it from time to time. Double-check the Web

properties on the web application project, and ensure that both ASP.NET and Silverlight debuggers

are checked.

15. Your application was running initially and then crashes after a few minutes with an exception message such as:

“Object reference not set to an instance of an object.. ---> System.NullReferenceException: Object reference

not set to an instance of an object”.

You may have encountered a problem that occurs when the IIS application pool has recycled. One of

the best ways to insure this does not happen is to create a new application pool that does not

recycle on a time limited basis and then assign your application to that pool.

16. Your application had been running and then crashes after you make a change to one or more of the files in the

application directory. The exception includes this message: “Could not load file or assembly

'App_Web_........”.

You may have encountered a problem that occurs when files in the application folder no longer

match the compiled version located in the “Temporary ASP.NET Files” folder. You can force a rebuild

Page 475: Dev Force 2010 Developers Guide

IdeaBlade DevForce Troubleshooting

475 | P a g e

of your application by deleting the “bin” folder and then replace it with a copy or by running the

“aspnet_compiler.exe” command with the “-c” switch. You can find the command by first browsing

to the folder “%SystemRoot%\Microsoft.NET\Framework\” and then open the v2.0.xxxxx subfolder

(the numbers after v2.0 can vary) . Here is an example using the virtual directory name of the

application: aspnet_compiler –v /MyApp -c

17. FIPS Compliance

If your Silverlight application will be served from a web server on which FIPS (Federal Information

Processing Standards) compliance is enforced, you will need to make the following changes to both

the web.config and startup pages.

In the web.config, you must set debug to false when FIPS is enabled. This is true even during

development: you cannot set debug to true with FIPS enabled!

XML

<system.web>

<!--

Set compilation debug="true" to insert debugging

symbols into the compiled page. Because this

affects performance, set this value to true only

during development.

-->

<compilation debug="false">

If you use an .html page (ex: index.html) instead of an .aspx page to host the Silverlight XAP, you will

need to delete:

XML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

in order to be compatible with Firefox.

18. How to tell if the BOS is running.

You’ve received an error message from your client application stating, “The remote server returned

an error: Not Found”. This is a communications error which occurs when the WCF client application

is unable to complete a handshake with the server. There are, unfortunately, a myriad of reasons

why this might occur, but one of the first things to check is if the service is actually running.

You can do this easily: open the web browser and point it to the URL which the client application is

using. For example, if the client app.config contains this...

Page 476: Dev Force 2010 Developers Guide

IdeaBlade DevForce Troubleshooting

476 | P a g e

C#

<objectServer

remoteBaseURL="http://localhost"

serverPort="9009"

serviceName="EntityService.svc" />

...then open the web browser to http://localhost:9009/EntityService.svc. If the service is running,

you will see a “Service description” page generated by WCF. If, instead, you see a page showing

error information, then you know the service cannot be started and that your application will be

unable to run. Usually the error message on the page has helpful diagnostic information.

19. Known Issues (Silverlight-Only)

The Copy Local property on DevForce references in the web project must be set to true for the apps

to run properly. This setting is required to allow the DevForce WCF services (defined in the *.svc

files) to be compiled correctly. If not set, the services will not start, the client application will be

unable to connect to the server, and you will see an error message as follows:

The remote server returned an error: NotFound. If the service is

unavailable, then also make sure that the endpoint bindings match

between client and server.

When you begin your Silverlight solution using the DevForce Silverlight Application project template,

several DevForce assemblies are added as references to the web project; and for all, CopyLocal is set

to true. However, if you manually add or modify references, you may see that the property is

initially set to false (which is the Visual Studio default). Always check this property when you see

the above error.

Contacting Support

Self Help Resources

DevForce installs with a number of resources for learning about Enterprise Application Development

with DevForce. There are manuals, guides, tutorials, code samples, and some movies.

Everyone has access to our forum (www.ideablade.com/forum) and the self-help features of our web-

site www.ideablade.com (see the Resources and Support menus there).

We are always updating the our site so please visit our support page for the most recent

news,

releases,

fixes,

Page 477: Dev Force 2010 Developers Guide

IdeaBlade DevForce Troubleshooting

477 | P a g e

help file(s)

tutorials,

videos,

reference applications and sample code, and

other support documents.

Paid Support

If you have an active Support Subscription, you can also submit a direct support case via our web site at

http://www.ideablade.com/CustomerSupportRequestForm.aspx

When submitting a support case, please try to simplify and isolate your problem as much as possible. If

you are submitting a bug report, creating a simple test case using the NorthwindIB database will help us

to quickly identify, correct, and test the issue.

IdeaBlade also offers Enterprise Support options as well as a full range of Professional Services to help

you build your application. Please contact your sales representative or email [email protected] for

more information.

If you have concerns about our support or need immediate assistance and you cannot reach one of our

support staff, please call your sales representative.

Identifying your DevForce version

From within Visual Studio

You can view the version number by

examining the property sheet of any

referenced “IdeaBlade” assembly.

Open the Solution Explorer

[Ctrl+W+S].

Open the References folder of

your Model or UI project (VB

developers must press the “Show

All” button first).

Select an “IdeaBlade” assembly.

Open its property sheet

[Ctrl+W+P].

Examine the “Version” property

near the bottom.

Page 478: Dev Force 2010 Developers Guide

IdeaBlade DevForce Troubleshooting

478 | P a g e

Outside Visual Studio

From the Windows Start menu, the DevForce version is shown on the shortcut:

You can also examine the DevForce assemblies in the installation directory which is typically located at

C:\Program Files\IdeaBlade DevForce 2010.

Hover the mouse over IdeaBlade.EntityModel.dll:

In a deployed application

The log file generated when your application runs, typically called Debuglog.xml, also contains the

version of DevForce in use:

Upgrading Your Software

We encourage you to upgrade your copy of DevForce to the latest version. The Installation Guide and

Release Notes provide detailed information about the versions and what steps (if any) you need to take

to upgrade your software.