You are on page 1of 553

p

DevForce

Developer Guide
Version 5.2.5
IdeaBlade DevForce Developers Guide Contents

Contents
DevForce, Enterprise Applications, and the ADO.NET Entity Framework .............................. 8
The Problem ..........................................................................................................................................................9
Object Mapping Technology ............................................................................................................................... 10
The Microsoft ADO.NET Entity Framework ...................................................................................................... 10
Using DevForce with the Entity Framework ...................................................................................... 13
Advantages of Using DevForce ........................................................................................................................... 14
DevForce in More Detail ....................................................................................................................... 16
Advantages of Using DevForce (Revisited) ........................................................................................................ 16
More DevForce Advantages ................................................................................................................................ 28
Conclusion .............................................................................................................................................. 34
Getting Started.............................................................................................................................. 35
Installation ........................................................................................................................................................... 35
DevForce Start Menu ........................................................................................................................................... 35
The “NorthwindIB" database .............................................................................................................. 37
Development Process ............................................................................................................................. 37
Hello, DevForce ........................................................................................................................... 41
DevForce Application Architecture - The Big Picture ........................................................................................ 41
DevForce and the ADO.NET EntityModel ......................................................................................................... 42
Your First DevForce Application: a Walk-Through ............................................................................................ 45
Building the Domain Model ................................................................................................................................ 45
Add a User Interface ............................................................................................................................................ 66
Add Unit Tests ..................................................................................................................................................... 68
Add a WinForm UI .............................................................................................................................................. 75
Understanding the App.Configs ........................................................................................................... 89
Information Flow Between the App.Configs ....................................................................................................... 91
Monitoring Activity ............................................................................................................................... 92
Appendix: Listings of Sample App.Config Files ................................................................................................. 94
Appendix: Probing Sequence for the App.Config File ........................................................................................ 95
Class Libraries.............................................................................................................................. 96
Important Namespaces ......................................................................................................................... 96
The IdeaBlade.EntityModel.Entity ...................................................................................................... 97
Finding Help on DevForce .................................................................................................................. 100
XML Documentation ......................................................................................................................................... 100
IntelliSense ........................................................................................................................................................ 100
The Object Browser ........................................................................................................................................... 102
Class View ......................................................................................................................................................... 103
Class Diagram.................................................................................................................................................... 103
Business Object Mapping .......................................................................................................... 105
Introduction ......................................................................................................................................... 105
Overview of the ADO.NET Entity Model ......................................................................................................... 106
Working with the IdeaBlade DevForce Object Mapper .................................................................................... 106
Object Mapper Walk-Through .......................................................................................................... 106

2|P age
IdeaBlade DevForce Developers Guide Contents

Exiting The Object Mapper ............................................................................................................................... 117


The Object Mapper Menus ................................................................................................................................ 118
Injected Base Types ........................................................................................................................................... 119
The Name Pluralizer: Fixing the Pluralization in Type Names ......................................................................... 121
Mapping a Web Service..................................................................................................................................... 124
Notes on the Generated Code ............................................................................................................. 127
Multiple Datasources .......................................................................................................................... 131
DataSourceKeys ................................................................................................................................................ 132
Appendix: Many-to-Many Associations in the Entity Framework................................................. 133
Property Interceptors ................................................................................................................. 140
Named vs. Unnamed Interceptor Actions .......................................................................................................... 141
Interceptor Chaining and Ordering .................................................................................................................... 142
Business Object Persistence....................................................................................................... 156
Note: Code Snippets in This Document............................................................................................................. 158
Object Persistence Overview .............................................................................................................. 158
The Big Picture .................................................................................................................................................. 158
DevForce and the ADO.NET EntityModel ....................................................................................................... 160
Support for POCOs (Plain Old CLR Objects) ................................................................................................... 162
Persistence Management Capabilities ............................................................................................................... 163
Entity Queries and Entity Navigation ............................................................................................... 169
Entity Queries .................................................................................................................................................... 169
Entity Navigation ............................................................................................................................................... 192
The Null Entity .................................................................................................................................................. 202
Asynchronous Communication with the Business Object Server ................................................... 203
Asynchronous Queries ....................................................................................................................................... 203
IAsyncResult Asynchronous Pattern ................................................................................................................. 206
Asynchronous Fulfillment of Navigation Property Queries .............................................................................. 206
Canceling Pending Operations........................................................................................................................... 206
The EntityListManager ...................................................................................................................... 207
Entity Caching ..................................................................................................................................... 210
All Business Objects are Cached ....................................................................................................................... 210
Queries, Navigation, and the Cache ................................................................................................................... 213
Query Workflow ................................................................................................................................................ 215
Query Strategy ................................................................................................................................................... 217
Span Queries ...................................................................................................................................................... 226
Cached Entity Lifespan...................................................................................................................................... 228
Saving the Cache Locally .................................................................................................................................. 228
The TraceViewer: Watch What Data Is Being Loaded, and How ..................................................................... 229
Creating Business Objects .................................................................................................................. 242
When Not to Create ........................................................................................................................................... 242
The Business Object Create Method ................................................................................................................. 242
Auxiliary Business Object Class Methods ......................................................................................................... 247
Adding and Removing Related Objects using Add() and Remove() ................................................................. 247
Business Object Creation Review...................................................................................................................... 249
Saving Business Objects...................................................................................................................... 250
EntityState of an Object ..................................................................................................................................... 250
Undo .................................................................................................................................................................. 250
Validation .......................................................................................................................................................... 250

3|P age
IdeaBlade DevForce Developers Guide Contents

Temporary Id Fix-up ......................................................................................................................................... 251


Life Cycle Events .............................................................................................................................................. 251
Saves and Transaction Management .................................................................................................................. 254
Re-query After Save .......................................................................................................................................... 255
When Save Fails ................................................................................................................................................ 255
Data Source Concurrency .................................................................................................................................. 258
Saving the “Dependency Graph” ....................................................................................................................... 263
Dependency Graph Retrieval ............................................................................................................................. 266
Workflow For a Save ......................................................................................................................................... 268
Saving the Cache to a Local Disk File ............................................................................................................... 270
XML Serialization of Business Objects ............................................................................................................. 271
Business Object Persistence – Advanced .................................................................................. 274
Getting Information About an Entity Type with GetEntityMeta() ................................................ 275
Access Both Local and Remote Data Sources In the Same N-tier Application ............................ 276
Stored Procedure Queries................................................................................................................... 277
SQL Server Stored Procedure Queries .............................................................................................................. 278
Stored Procedure Entity Navigation.................................................................................................. 281
Forced Re-fetch ................................................................................................................................... 282
Lost Connection During Query .......................................................................................................... 283
Query Cache ........................................................................................................................................ 283
EntityManager.RemoveEntities Overload Preserves Query Cache ................................................................... 284
MergeStrategy In More Detail ........................................................................................................... 285
The EntityManager.AttachEntity Method........................................................................................ 289
Filtering Queries .................................................................................................................................. 291
Query Inversion in More Detail ......................................................................................................... 293
Transactional Queries ......................................................................................................................... 297
DevForce and Data Sources – Deep Dive .......................................................................................... 297
The Object Mapper and Manually Added or Modified Keys ............................................................................ 299
DataSourceKeys, DataSourceKeyResolvers, and DataSourceExtensions ......................................................... 299
EntityManagers and DataSourceExtensions ...................................................................................................... 299
Tenant Extensions.............................................................................................................................................. 302
Multi-Part Extensions ........................................................................................................................................ 303
Extensions and EntityServers ............................................................................................................................ 304
Dynamic DataSourceKeys and the DataSourceKeyResolver ............................................................................ 304
Multiple Application Environments .................................................................................................. 307
Multi-Level Undo with Checkpoints.................................................................................................. 307
Multiple EntityManager Instances .................................................................................................... 309
Multi-Threading in a DevForce App ................................................................................................. 310
Batching Asynchronous Tasks ........................................................................................................... 312
Service Oriented Architecture ........................................................................................................... 314
POCO Support in DevForce............................................................................................................... 315
Examples of POCO Classes ............................................................................................................................... 316
Examples of a POCO Service Provider Class .................................................................................................... 318

4|P age
IdeaBlade DevForce Developers Guide Contents

Example of a Client-Side Class Containing Extension Methods for the EntityManager ................................... 320
Obtaining an EntityAspect Property on Your POCO Object ............................................................................. 321
Data Contract Serializer (DCS) versus .NET Data Contract Serializer (NDCS) .............................................. 322
POCO Save mechanisms ................................................................................................................................... 326
Summary – Things to Remember When Using POCOs in Your DevForce App .............................................. 329
Validation Through Verification ............................................................................................... 330
DevForce Verification ....................................................................................................................................... 331
Getting Started .................................................................................................................................... 332
Validation-Related Settings In the Object Mapper ............................................................................................ 332
Generated Property Code ................................................................................................................................... 334
Impact of Verifiers on the User Interface – A Caution ...................................................................................... 338
Now That You‟ve Been Initiated (and Before We Enter the Forest): A Quick Overview of the
Mechanics............................................................................................................................................. 339
Verification Types Overview .............................................................................................................. 340
Main Verification Classes.................................................................................................................................. 340
Verifiers ............................................................................................................................................................. 341
VerifierResult .................................................................................................................................................... 344
Triggers.............................................................................................................................................................. 347
VerifierEngine ................................................................................................................................................... 348
PropertyValueVerifiers ...................................................................................................................................... 350
Verification Deep Dive ........................................................................................................................ 355
Verifiers ............................................................................................................................................................. 355
Verifier Result ................................................................................................................................................... 359
Triggers.............................................................................................................................................................. 362
VerifierEngine ................................................................................................................................................... 370
Invoking Verification .......................................................................................................................... 375
Instance Verification .......................................................................................................................................... 376
Trigger Verification: Preset and Postset ............................................................................................................ 377
Monitor Execution with the VerifierBatchInterceptor ....................................................................................... 381
Verification and WinForms User Interfaces ..................................................................................... 382
UI Lockup .......................................................................................................................................................... 382
Improving the User‟s Experience ...................................................................................................................... 384
DevForce Silverlight Apps ......................................................................................................... 386
Overview - What is DevForce Silverlight? ........................................................................................................ 386
Creating a DevForce Silverlight Application .................................................................................................... 386
Silverlight Deployment Steps ............................................................................................................................ 387
Questions and Answers...................................................................................................................................... 387
Troubleshooting ................................................................................................................................................. 389
WinForm User Interfaces .......................................................................................................... 393
UI Data Binding ................................................................................................................................... 394
NET Data Binding ............................................................................................................................................. 394
NET v. DevForce WinClient UI Data Binding for WinForms .......................................................................... 395
Data Binding with DevForce WinClient UI Designers For WinForms ............................................................. 397
DevForce WinClient Data Binding Architecture ............................................................................................... 399
Nested Property Paths ........................................................................................................................................ 413
Data Binding to Data Objects of Any Type ....................................................................................................... 418
When to Use .NET Data Binding Instead .......................................................................................................... 421
When Not to Use Data Binding at All ............................................................................................................... 422
UI Architecture .................................................................................................................................... 423

5|P age
IdeaBlade DevForce Developers Guide Contents

Nested Property Paths ........................................................................................................................................ 423


The BindableList(of T) ...................................................................................................................................... 423
EntityPropertyDescriptors ................................................................................................................................. 439
UI Designers ......................................................................................................................................... 443
BindingManagerDesigners ................................................................................................................................ 443
More on Third-Party WinForm Control Suites ............................................................................... 459
Developer Express “DXperience” ..................................................................................................................... 459
Infragistics “NetAdvantage” .............................................................................................................................. 460
DataBinders ......................................................................................................................................... 460
Troubleshooting ................................................................................................................................... 461
Third-Party Control Suites ................................................................................................................................. 461
UI Performance Tuning ..................................................................................................................................... 462
Large BindingSource loads are Slow ................................................................................................................. 463
DevForce WinClient Assemblies for WinForm Support ................................................................. 463
Web Applications........................................................................................................................ 465
The DevForce ASPDataSource Component ...................................................................................................... 465
Using the ASPDataSource in Development ...................................................................................................... 465
Overridable Methods for Select, Update, Insert, and Delete ............................................................................. 465
The EntityAdapterManager Class ...................................................................................................................... 466
The Configure Data Source Wizard ................................................................................................................... 467
Parameter Collection Editor .............................................................................................................................. 467
Retrieving Schema Information ......................................................................................................................... 468
Third Party Support ........................................................................................................................................... 468
Business Object Server............................................................................................................... 469
Business Object Server Architecture ................................................................................................................. 469
EntityService Startup and Shutdown ................................................................................................................. 472
EntityServer Startup and Shutdown ................................................................................................................... 473
Remote Service Method Call (RSMC) Methods ............................................................................................... 473
Push Notification ............................................................................................................................................... 475
BOS Hosting Details ............................................................................................................................ 476
The DevForce Client ......................................................................................................................................... 478
Vista Setup ........................................................................................................................................... 479
Vista setup requirements for the ServerConsole or ServerService .................................................................... 479
Vista setup requirements for IIS ........................................................................................................................ 479
Troubleshooting ................................................................................................................................... 480
Worked in 2-Tier, Strange Errors in n-Tier ....................................................................................................... 480
Disconnected Applications......................................................................................................... 482
Running Offline ................................................................................................................................................. 483
Securing Offline Data ........................................................................................................................................ 492
Security ....................................................................................................................................... 497
Authentication ................................................................................................................................................... 497
Authorization ..................................................................................................................................................... 500
Encryption ......................................................................................................................................................... 502
ASP.NET Security Integration .......................................................................................................................... 502
Deployment ................................................................................................................................. 506
Document Overview ............................................................................................................................ 507

6|P age
IdeaBlade DevForce Developers Guide Contents

DevForce And the App.Config File.................................................................................................... 507


Creating and Editing a Configuration File ......................................................................................................... 508
IdeaBlade DevForce Configuration Editor ........................................................................................................ 508
DevForce Elements in App.Config .................................................................................................................... 510
Configuration File Location .............................................................................................................................. 511
Client and Server Versions of App.Config ........................................................................................................ 512
Probing in DevForce .......................................................................................................................................... 513
Data Server Deployment ..................................................................................................................... 516
Deploying a DevForce Silverlight Application.................................................................................. 516
Deploying to IIS Version 6 ................................................................................................................................ 517
Deploying to IIS Version 7 ................................................................................................................................ 519
Troubleshooting ................................................................................................................................................. 522
Resources ........................................................................................................................................................... 523
Deploying a DevForce WinClient Application.................................................................................. 523
Overview ........................................................................................................................................................... 523
Deploying a Single-Tier WinClient Application ............................................................................................... 526
Deploying Two-Tier (Client-Server) WinClient Applications .......................................................................... 526
Deploying N-Tier (Smart-Client) Applications ................................................................................................. 527
Building Blocks ................................................................................................................................................. 527
Troubleshooting ......................................................................................................................... 547
General Troubleshooting .................................................................................................................... 547
Troubleshooting Silverlight Apps ...................................................................................................... 548
Contacting Support ............................................................................................................................. 551
Identifying your DevForce version .................................................................................................................... 551
Upgrading Your Software .................................................................................................................. 553

7|P age
IdeaBlade DevForce DevForce and the Entity Framework

DevForce, Enterprise Applications, and the


ADO.NET Entity Framework

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


The Problem
Object Mapping Technology
The Microsoft ADO.NET Entity Framework
Using DevForce with the Entity Framework
Advantages of Using DevForce
DevForce in More Detail
Advantages of Using DevForce (Revisited)
More DevForce Advantages
Conclusion

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 task 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 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 all levels of the enterprise application architecture stack, its 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, both released with version 3.5 of the .NET 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.

8|P age
IdeaBlade DevForce DevForce and the Entity Framework

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:
 Central services, Internet connectivity, 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 Web and WCF service data sources
 Proper support for a business object layer with business rules.

DevForce satisfies these requirements even as it relies on the ADO.NET Entity Framework for basic ORM and query
facilities. The key components of DevForce include:
 the Entity Manager, 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
 the Object Mapper which extends the ADO.NET Entity Framework designer and generates DevForce
entity code.
This chapter explores the key data management issues for .NET enterprise application developers. It introduces the
LINQ and the ADO.NET Entity Framework, explaining what they do and where they leave off. It then describes
how DevForce fills in the critical gaps.

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

9|P age
IdeaBlade DevForce DevForce and the Entity Framework

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 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 databases objects 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.
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 one Object Mapping technology to consider. DevForce builds upon
the Entity Framework, so we introduce the Microsoft technology here before explaining DevForce‟s added value.

10 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

Read more about the Entity Framework at http://msdn2.microsoft.com/en-us/library/bb399572(VS.90).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 ADO.NET 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.

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.

11 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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 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.

12 | P a g e
IdeaBlade DevForce DevForce and 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.

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 ADO.NET 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 for applications built on the .NET 3.5 platform.

DevForce provides an alternative Entity Data Model editor, the DevForce Object Mapper, which is used for four
main reasons:
 to augment the EDM schemas with DevForce-specific XML
 to generate the DevForce business object classes which extend the Entity Framework classes
 to work with a tabular interface that is more productive for larger (>20 class) object models
 for more granular control over the generated class and property code
The Object Mapper plugs into Visual Studio and the developer can switch freely between the Object Mapper and the
Entity Framework designer, choosing the one that is most productive for the task at hand.
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.

13 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

Advantages of Using DevForce


The ADO.NET 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 multi-tier applications
 Security
 Client application performance
 Model design and code generation
 Multiple data sources
 Web Services
 Intermittently connected and offline apps
We summarize each point in the balance of this section.

Infrastructure for Multi-Tier Applications


The ADO.NET 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).
DevForce is the only way to bring n-tier capabilities to LINQ- and Entity Framework-based applications.

Security
ADO.NET 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

14 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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 ADO.NET 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. 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.

Model Design and Code Generation


The ADO.NET Entity Framework design tools and code generation are not as strong as they need to be for
enterprise-scale applications. The drag-and-drop designer becomes unwieldy with modestly sized domain models;
class diagrams with more than 20 objects are almost impossible to read or manage. The developer has little control
over the mapping and the generated code doesn‟t support common business behavior scenarios such as validation,
property-level security, value and message localization, and change auditing.
The DevForce Object Mapper can read and write EF schemas. Its utilitarian interface targets medium to large
models (20 to 2,000 entity types). It‟s easy to determine how data are exposed as classes and properties and it‟s easy
to grow the model as requirements change. It can generate classes from storage schema but it also tolerates
conceptual class development in advance of storage mapping. The generated code is designed for augmentation with
business logic so we can build business objects instead of property bags.

Multiple Data Sources


The ADO.NET 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.

Web and WCF backed Business Objects


Web services and WCF services are increasingly important sources of application data both as front ends to legacy
databases and as the preferred modality for accessing Internet resources.

15 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

The ADO.NET Entity Framework can only map entities to relational databases. DevForce can map business objects
to relational databases, web services, and WCF services, all in a single consolidated model.

Intermittently Connected and Offline Applications


ADO.NET 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.

Advantages of Using DevForce (Revisited)

Multi-Tier Applications
The ADO.NET Entity Framework is a client / server technology. It’s 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.

16 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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.

The ADO.NET 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 ADO.NET 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.

17 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

DevForce Silverlight 1

Features described in the section are included with the DevForce Silverlight product.

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
ADO.NET Entity Framework cannot move data over the Internet. But a DevForce Silverlight application can.
In 2009, IdeaBlade will release “DevForce Silverlight” supporting SilverLight applications that are based on the
same rich object model deployed in DevForce WPF smart client applications.

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 the DevForce Silverlight product, 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;”.

1
DevForce Silverlight and DevForce WinClient are separate IdeaBlade products. They are combined in the DevForce
Universal product.

18 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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.
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.

NT 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 ADO.NET 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. 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.
The ADO.NET Entity Framework caches entities and can look up an object by key rather than go to the database.
That can be a big time saver – unless the entity isn‟t in cache! The Entity Framework returns null if it can‟t find the

19 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

object. Maybe the entity doesn‟t exist. Maybe it just hasn‟t been retrieved yet. The Entity Framework can‟t tell.
We‟d have to query for the object – or explicitly load it – to be sure.
Unfortunately, we can‟t simply query the Entity Framework cache directly. All Entity Framework queries (and
loads) reach across the net to the database – even repeat queries issued mere moments ago.
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 an arduous and error prone pursuit best left to the DevForce framework.

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.
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);

20 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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 synchronously2; 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 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

2
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.

21 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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.

ADO.NET Entity Framework Development


The ADO.NET Entity Framework takes a stab at the appropriate tooling and code generation. There is a Visual
Studio EF designer that presents a visual canvas upon which to draw entity classes, the relationships among them,
and the mapping to the storage schemas.
The EF 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.

Weaknesses

The Entity Framework designer and generator fall short in several critical 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.

Designer Woes
We could write the properties by hand. But we‟d like to use the Entity Framework Designer to generate the code for
us so that the property code conforms to standard and includes all the property interceptor calls it should have.
Unfortunately, the Entity Framework Designer won‟t generate entity code without validating the storage model and
the mapping schema – which don‟t yet exist.
The DevForce Object Mapper can generate the tedious persistent data accessor property code with the conceptual
model alone. It doesn‟t need the storage or mapping schemas which we can fill in later.
The developer should be able to build the conceptual data model without first committing to a storage or mapping
specification. When the developer determines that the application data requirements are sufficiently well known to
warrant database schema design, she can add the storage and mapping schemas “just in time.”
Unfortunately, the EF insists that every conceptual entity be mapped. It refuses to “validate” the model when there is
no mapping and it won‟t generate code for an un-validated model.
The developer should work on just those business objects that are pertinent to the user story. Who cares if the
database has hundreds of tables when we only need five business objects. Sadly, the EF designer is unforgiving.
There is no going back once the developer has selected his tables and generated her model. She will have to edit the
XML to add the sixth, seventh, and eighth objects.

22 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

Indeed, there are a great number of everyday mapping activities that can only be accomplished by dipping into the
raw XML.

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 EF 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.
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.

23 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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.
 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 Design and Code Generation


The DevForce Object Mapper and code generation address each of these deficiencies.
The Object Mapper is a Visual Studio plug-in accessible from the tools menu. It presents classes and mappings in
the grid format familiar to DevForce developers today. It surrenders to the Entity Framework all of the drag-and-
drop finery of pretty boxes arranged on a stylish canvas. It favors a proven utilitarian approach that grants the
developer unfettered access to large object models.
The developer can add classes that are not yet mapped to a database table (or web method) and generate the entity
classes. These classes can‟t be persisted until they are mapped. But they can be elaborated to support user stories and
they can be tested.
Call it impure if you must but it is a huge time saver to generate part of the conceptual model from existing database
tables or web methods. You can do so incrementally. If you only need five objects, that is all you map. It‟s easy to
come back later to generate additional storage-backed business object classes.
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 the DevForce Object Mapper 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

24 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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 chapter “Property Interceptors” in this Developer Guide.

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.
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 EF 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:

25 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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.

Web Service Data Sources


The Entity Framework only works with relational data. It can only communicate with a relational database server.
Not all data can be reached via a relational database server. Sometimes the data are locked up in a legacy non-
relational database. Sometimes the database is guarded by corporate IT or walled in behind a vendor‟s proprietary
API. We may have to access such data sources through a web service.
Our application may have to reach outside the corporate walls to access data from external sources. Tax rates, credit
scores, geographical data, and zip codes are some of the external resources our application might expect to acquire.
Most of these resources are already exposed as web services and those that aren‟t can be wrapped in a web or WCF
service by a moderately skilled developer.
Web service data are every bit as resistant to object oriented treatment as relational data. There is the same
disconnect between the storage model and the conceptual model. Our object mapping technology should support
entity classes backed by web service data. The upper application layers should be not be reminded constantly of the
underlying storage technology.

Entities of a DevForce conceptual model can be mapped to Web and WCF Service methods as illustrated here:

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

26 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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.

27 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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 ADO.NET 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.
The EF design team seems to have succumbed to architectural Puritanism. OO 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.
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 OO 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.

28 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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 EF 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 EF query with the “overwrite” option with refresh the unmodified inventory level – and wipe out the user‟s
pending changes to other inventory objects.
An EF 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 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 EF 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

29 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

EntityMetadataStore class. See the section “Getting Information About an Entity Type with GetEntityMeta()” in the
Object Persistence chapter 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.
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 illustration, 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.

30 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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 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).

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 EF 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 EF knows, post-save, to read the new value from the back-end data store and update
the entity in the EF cache.
The EF 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.

31 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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 EF 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 EF 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 EF assigns it a temporary key (e.g., “-1”). When we add a new
OrderDetail to that Order, EF 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 EF acquires the permanent ids from SQL Server
and updates the objects accordingly. Continuing our example, the EF 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”.
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.

32 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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 the DevForce Object Mapper 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.

Undo and Checkpointing


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.
There is no progressive undo capability in Entity Framework. With the DevForce “Checkpointing” facility, the
application can roll back the state of the entire entity cache to any one in a sequence of “checkpoints” or snapshots.
Wizards put this feature to good use. Each step forward through the wizard can be marked. In the current step the
user might add new entities, modify or delete existing entities, and retrieve more from the database. If the user then
cancels the current wizard page and retreats a step, the application can discard all of these changes and restore the
state of both the entities and the cache to the marked state with a single command.

Sandbox Editors
Sandbox editors are a convenient alternative – or compliment – to checkpointing. 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.

33 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework

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.
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 the early days of .NET. 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 ADO.NET 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.

34 | P a g e
IdeaBlade DevForce Getting Started

Getting Started

Getting Started
Installation
DevForce Start Menu
The “NorthwindIB" database
Development Process

This section offers a brief overview of how to get started with IdeaBlade. Topics covered in this chapter are:

Topic Description
Installation Brief introduction and pointer to pertinent sources.
DevForce Start Menu Tools and information accessible from the Windows Start Menu.
NorthwindIB database Many examples make use of the tutorial NorthwindIB database, which is
based on Microsoft's blueprint NorthwindEF database.

Documentation Best Practices indicators and typographical conventions used in this guide.
Conventions

Installation
The separate DevForce Installation Guide covers the installation and upgrade process in detail and also contains a
troubleshooting section. The DevForce Release Notes contain version specific information that you may need for
certain upgrades.

DevForce Start Menu


Installation adds an “IdeaBlade DevForce” folder to your Start menu. At the moment it looks like this:

35 | P a g e
IdeaBlade DevForce Getting Started

Documentation
Description
Menu Item
Developers Guide The document you‟re reading now.
DevForce Help The technical help covering the DevForce assemblies, types, and type
members.
Installation Guide How to install and upgrade DevForce. Includes Troubleshooting tips.
Learning Units Scripts and solutions for hands-on walk-thrus of DevForce product features
and applications.
Release Notes Documentation of new features, enhancements to existing features, bug fixes,
upgrade issues, and everything else you need to know when upgrading your
copy of DevForce.

36 | P a g e
IdeaBlade DevForce Getting Started

Applies to
Tools Menu Item Description DevForce
Silverlight
Assembly Binding Discovers third-party control suites on your machine; compares the No
Redirector names and version numbers of the associated DLLs with the versions
supported by DevForce; suggests redirections to permit you to use your
installed versions with DevForce; and writes machine.config statements
to implement the selected redirections.
Config Editor Edits an IdeaBlade.ibconfig file governing your application‟s Yes
deployment. DevForce application deployment is its own chapter in this
guide.
Database Installer Installs the NorthwindIB sample database. This is a SQL Server Yes
database (not a database server) that is used in many of the Learning
Units that accompany the product.
N-Tier Quick „n dirty tool that facilitates testing a distributed app. Creates No
Configuration folders for client- and server-side assemblies and configuration files,
Starter and otherwise facilitates running the DevForce EntityService in a
separate process from the client side app and EntityManager.
Product Key Facilitates replacing your current product key with a new one (in the Yes
Updater case of upgrade, etc.)
Tool Box Installer When you elect installation of Windows Forms support (the default), No
DevForce adds a number of visual design components to the Visual
Studio 2008 Tool Box. Sometimes VS won‟t accept our automated
attempt to install these tools. You may remove one or more from the VS
tools accidentally. You may acquire a 3 rd party control suite for which
we have a dedicated visual component. This installer will help you
(re)install these components.
Trace Viewer Tool for listening to logged activity from a running DevForce Yes
application.

The “NorthwindIB" database


NorthwindIB is a sample database that is referenced by the DevForce Tutorials and other documentation. It differs
from its source, the Microsoft NorthwindEF database, in several significant respects while retaining a recognizable
parentage. The data are mostly the same.
The differences between NorthwindIB and NorthwindEF are detailed in a text file
“NorthwindIB_DifferencesFromEF.txt” which installs in the DevForce installation directly alongside the
NorthwindIB.MDF database file.
We recommend installing or upgrading your copy of "NorthwindIB" so you can follow along with our tutorials and
code samples. The normal installation process tries to add this to your SQL Server databases but it may fail to do so
for any number of reasons. We also update this database from time to time in order to support new example code
that illustrates DevForce features.
Please see the Installation Guide for instructions on how to install or upgrade this database.

Development Process
DevForce promotes a distinctive process for development of distributed, object-oriented, enterprise applications.

37 | P a g e
IdeaBlade DevForce Getting Started

The “object-oriented” and “distributed” parts may seem a little foreign to some.
The object-oriented approach to data means thinking in terms of Business Objects and Object Persistence rather than
retrieving, inserting and updating data records. This becomes so obvious and easy in DevForce that, in a few days,
you stop thinking in terms of fields and joins and you may even forget how to use ADO.
The “distributed” aspects don‟t surface until well down the road and, because it‟s easy to re-configure the
application for multiple physical tiers, there is no cost to delaying awareness of multi-tier considerations.
Many developers do the lion‟s share of their work in a one-tier physical model in which all components of the
system – even a test database – reside on a single physical machine. You may prefer to access test data on an
independent server in which case your development experience is not that much different than good-old client /
server.
The following sections summarize the stages in a typical development process. The summary highlights the end-to-
end influence of the DevForce infrastructure.

Database Schema Implementation


You either have a DBA or you are the DBA. If you are the DBA, you‟ve always been in control. If you have a DBA,
you‟re going to have to coordinate with her. Fortunately, she can remain the only one who touches the database.
You may recommend schema changes but nothing in DevForce requires a schema change.
There may be a tussle over stored procedures. The DBA may want you to use them. You can. But your life will be
much better without them in most cases.
DevForce assumes you are starting from an existing database schema. Theory says the object model should dictate
the storage schema. This is highly desirable, especially in the early design phases. However, once your
application(s) have settled in, the “Model Driven Architecture” (MDA) approach becomes academic. The database
is what it is and you may change it only at increasing cost.
DevForce does not provide an MDA tool. Neither does it interfere with MDA. It picks up where MDA leaves off.
Of course your schema doesn‟t stand still either. DevForce adapts to those changes without imposing any of its own.

Object Mapping
The application architect or senior developer uses a combination of the Entity Framework‟s Entity Data Model
(EDM) Designer and the DevForce Object Mapper to configure the map business object classes to data source
objects. While “data source objects” can reside in databases, web service methods, or message queues, most
enterprise application data are stored in relational databases.
Accordingly, most object mapping is between business object classes, AKA entities, and tables, views, or stored
procedures in a relational database.
You cycle around and around from schema design to database schema change to object mapping to redesign. You
return repeatedly to the Object Mapper, knowing that it adapts to change while your business object layer rides
above the mapped classes, insulating the UI layers of your application from adverse consequences of those changes.
The DevForce Object Mapper and the Entity Framework EDM designer co-exist happily: neither interferes with the
other‟s work.

Business Logic Elaboration


That business object layer is the locus of business logic development.
You begin with a collection of use cases and test cases that explore the persisted features of the Business Objects.
You might read simple entity properties and explore the network of relations among the entities, perhaps printing
results to the console.

38 | P a g e
IdeaBlade DevForce Getting Started

Business Object creation and comparison methods are next, following the IdeaBlade recommended patterns and
using a few simple methods of the Entity class at the root of all Business Objects.
Note that we‟re not writing UI here. We‟re exploring and enriching the business objects independently of any
particular user experience. Our changes go in the developer partial classes that are initially generated by the Object
Mapper and subsequently left entirely alone.
Slowly we begin to add rules and to verify those rules. The ship date must be after the order date, for example. Only
a user with administrative rights can change a salary. The developer inscribes these rules in the custom entity classes
that comprise the business logic layer.
The developer doesn‟t spend much time thinking about how to push and pull entities from their persistent homes in
data storage. That‟s the job of the EntityManager, guided by the object map. Developers no longer embed SQL
commands for reading or writing data to storage. Instead they invoke strongly-typed LINQ queries that return
business objects (entities). They write business logic that references object properties, not data fields.

Nonpersisted Classes
Business objects have state and long-lasting identity. They are stored (persisted) for an extended time.

Not everything is a business object. There are transient objects and Singleton classes with no state to store such as
temporary collections (e.g. list of user-selected products).
calculation classes (e.g., a ROI calulator).
helper classes to which similar Business Objects delegate common functionality (e.g., audit logging).
APIs to external applications.
Because these are not "Business Objects" - they do not carry state (or least what state they have is not stored in the
database). They are not mapped and they fall outside the purview of DevForce Object Persistence.
Such classes are written in the normal fashion and will be collected in one or more separate application projects.

Application Control Classes


The developers add the control classes that manage navigation and work flow. They enable the graphic designer‟s
buttons and menus and tie the forms to other methods in the business objects.

Deploy
The application is completed in record time. There have been mock deployments to development, test, and staging
environments. The production deployment is no different.
The application code itself is identical, whether it is installed on the server or deployed to a client; the only
difference is the configuration of their respective IdeaBlade Configuration files which are now separately edited for
the production environment.
There are packages of files: a server package and a client package.
The server package typically is a Microsoft Install file (MSI). This file is unpacked into the designated directories of
one or more servers and each is launched. The Business Object Server monitors the health and activity of each
server application.

DevForce WinClient
.NET‟s “ClickOnce” publishing is the easy way to build and distribute the client package.

39 | P a g e
IdeaBlade DevForce Getting Started

ClickOnce puts the package (a collection of files) on a web server and makes it accessible through a web page. PC
users with .NET runtime installed navigate by browser to the page and clicking an install button.
ClickOnce downloads and installs the client application in the user‟s personal directory. It then launches
automatically. This install happens once. Developers upgrade the application and publish revised client packages.
ClickOnce detects the upgrade and downloads the new version seamlessly.
There is no danger of the application executables and class libraries colliding with those of other application. Nor
will an install (or uninstall) of a different application disturb this one. .NET has corrected the DLL nightmare with
proper versioning. .NET applications don't have to use the Windows registry either.

DevForce Silverlight
In a Silverlight application, the XAP file is your client package, and Visual Studio has fortunately built it for you.
You‟ll need to be sure to place the XAP file in the ClientBin folder of the web site which is hosting the application.
This site will typically also host the Business Object Server (deployed as the server package above) but this is not a
requirement. If you do host the BOS and Silverlight application from different web sites you‟ll need to be sure to
also deploy a file named “clientaccesspolicy.xml”. More information on this is available later in this document.

40 | P a g e
IdeaBlade DevForce Hello DevForce

Hello, DevForce

Hello, DevForce
DevForce Application Architecture - The Big Picture
DevForce and the ADO.NET EntityModel
Your First DevForce Application: a Walk-Through
Building the Domain Model
Add a User Interface
Add Unit Tests
Add a WinForm UI
Understanding the App.Configs
Information Flow Between the App.Configs
Monitoring Activity
Appendix: Listings of Sample App.Config Files
Appendix: Probing Sequence for the App.Config File

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, highlighting some of the more prominent DevForce features
along the way.

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.

41 | P a g e
IdeaBlade DevForce Hello DevForce

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. If the back-end data store is a relational database, the EntityServer leaves the direct
communication with the data store to the ADO.NET Entity Framework. However, if the back-end data store is a web
service, the DevForce EntityServer handles the job, since that capability does not exist within the 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 (besides handling those jobs against web services), 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.

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. It also renders the object model in code in a file named <ModelName>.Designer.cs (or .vb).

42 | P a g e
IdeaBlade DevForce Hello DevForce

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 and the corresponding generated code 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.
The second step will be to create a Domain model using the DevForce Object Mapper. This model is so named
because it will be composed of one or more Entity Models persisted in .EDMX files.
The DevForce Object Mapper will alter the .EDMX file by adding additional elements and attributes. These added
features are ignored, and left undisturbed by, the ADO.NET Entity Data Model Designer. Because of this, the
developer can move back and forth between the Visual Studio Entity Model Designer and the DevForce Object
Mapper without fear of either disturbing the other‟s work.
There is, by intent, some overlap in the the functionality of the DevForce Object Mapper and ADO.NET Entity Data
Model Designer. However, our intial work on the DevForce Object Mapper has been focussed on providing needed
or useful capabilities that are either not present, or are difficult to work with, in the Entity Data Model Designer. Our
goal is to make it as convenient as possible for you to work with your model.
We mentioned that the Entity Data Model wizard and designer, in addition to altering the .EDMX file, generate the
classes that comprise the compilable manifestation of the object model. From the Object Mapper‟s enhanced version
of the .EDMX DevForce generates two sets of classes. The first is essentially the same Entity Framework model
generated by the Visual Studio tools. This version of your object model will be deployed to the logical middle tier
of your application, where it is used by the ADO.NET Entity Framework for creating objects of the type that it
understands.
The second version of the object model generated by the DevForce Object Mapper is a DevForce version consisting
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”: unlike the Entity
Framework model, 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 DevForce Domain Model is the only client-side model your application will use. It is, however, also deployed
server-side; and it‟s scope of operation is synonymous with the bracketed area labelled “DevForce Business
Objects” in Figure 1.
The Domain Model is a consumer of Entity Data Models, whose .edmx files typcially define the lion‟s share of its
content. Server-side, DevForce delegates to the Entity Framework the jobs of communicating with the database(s) to
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.
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).

43 | P a g e
IdeaBlade DevForce Hello DevForce

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.

What are the Parts of Your Business Model, and Where Are the Parts Deployed?
The ADO.NET Entity Data Model and the DevForce Domain Model each have representations in both
XML and in .NET code.
The representation of the Entity Data Model (EDM) in XML is a file with the extension .EDMX. Visual
Studio includes a code generator that creates a corresponding file of .NET code. This file has the same
name as the .EDMX file, but an extension of “designer.cs”. It is stored by Visual Studio subordinate to the
.EDMX file in the Entity Data Model project.
The representation of the DevForce Domain Model in XML consists of a file with the extension .IBEDMX;
and one or more of the Entity Data Model (.EDMX) files just discussed. The .IBEDMX file mostly acts as
a catalog of the Entity Data Models that contain, in XML, the detailed specifications of entities, properties,
associations, tables, columns, relationships, and mappings. Both the DevForce Object Mapper and the
Visual Studio Entity Data Model Designer read from and write to the .EDMX files. The tools cooperate
completely, fully respecting each others‟ work, and may be used in any order.
Using the specifications stored in the .IBEDMX and .EDMX files, the DevForce Object Mapper generates
a file of .NET code which has the same name as the .IBEDMX file, but an extension of “designer.cs”. This
generated code file is stored by the Object Mapper subordinate to the .IBEDMX file in the Domain Model
project.
The Object Mapper also generates “developer partial class” files for each entity in the Domain Model.
These files are named “<EntityName>.cs” and are generated into the Domain Model project.

44 | P a g e
IdeaBlade DevForce Hello DevForce

Your First DevForce Application: a Walk-Through


With that information, we‟re ready to walk through the process of building a simple DevForce-based application.
The process consists of the following steps:

1. Obtain or create at least one ADO.NET Entity Data Model


2. Create a new Domain Model using the DevForce Object Mapper
3. Add the Entity Data Model to the Domain Model
4. Adjust the Entity Data Model as desired; e.g., rename classes and properties, designate concurrency
columns, etc.
5. Save the Domain Model, which results in code being generated for the types in the model
6. Optionally, add additional Entity Data Models and repeat
7. Optionally, add entities backed by web-services
8. Add custom business logic to the entity classes
9. Add Unit Tests
a. Add References
b. EmployeeTest First Look
c. Get a Test Employee
d. Run the Test
e. Accumulating Test Results
f. Lessons Learned
10. Create the UI
a. Unaided .NET Winforms
b. .NET Winforms Using DevForce V3 UI tools
c. WPF
Let‟s get started!

Building the Domain Model


1. We‟ll begin our walk-through by creating a blank Visual Studio solution named “DevForce01”:

45 | P a g e
IdeaBlade DevForce Hello DevForce

To that we‟ll add an empty project into which we‟ll subsequently put a newly created Entity Data Model. If
you know you‟ll be starting with an existing solution that already includes one or more Entity Models, you
can skip ahead to the section, “Build Your Domain Model Using the DevForce Object Mapper”.

2. Add a new Class Library project to your blank solution. Name this project “ServerModelNorthwindIB”.
We‟re naming it this because it will house an Entity Data Model, which would only be deployed server-
side in an n-tier deployment; and because that Entity Data Model will be based upon the NorthwindIB
database.

3. Delete the Class1.cs file that gets created by default in the new project. You will not use it.

4. Add a New Item, an ADO.NET Entity Data Model, to the project using the Entity Data Model wizard.
Name your model ServerModelNorthwindIB.edmx.

a. On the Choose Model Contents dialog, select “Generate from database”.

b. On the Choose Your Data Connection dialog, create or select the NorthwindIB data connection.
Rename the connection settings key name for the App.Config file to
“ServerModelNorthwindIBContext”. (That name will end up being used for the .NET
ObjectContext that will be generated by the Entity Framework.)

46 | P a g e
IdeaBlade DevForce Hello DevForce

c. On the Choose Your Database Objects dialog:


 Uncheck the Stored Procedures and Views.
 Expand the Tables node and make sure only the following tables are checked:
 Customer
 Employee
 Order
 OrderDetail
 Product
 Supplier

47 | P a g e
IdeaBlade DevForce Hello DevForce

 Rename the Model Namespace to “ServerModelNorthwindIB”


 Click <Finish>.

5. Visual Studio will create an Entity Data Model using the settings you specified, and will open it in its
graphical editor. Save the file without making any changes, then inspect it in the graphical editor.

Note the associations (“relationships” in database parlance) among the various entities. Order, for example,
has associations to the Customer, Employee, and OrderDetail entities. It‟s on the “many” end of 1-to-many
associations with Customer and Employee; it‟s on the “1” end of a similar association with OrderDetails.

Notice the corresponding Navigation Properties that were generated by the Entity Data Model wizard:
Customer, Employee, and OrderDetails.

48 | P a g e
IdeaBlade DevForce Hello DevForce

Note also that the Employee entity has a


relationship to itself, representing the reporting
relationship among NorthwindIB Employees. The
Entity Data Model wizard modelled the
relationship and generated navigation properties,
but lacking much understanding of what the entities
on the two ends of that relationship represent,
simply named them “Employee” and “Employee1”.
At the same time it created navigation properties in
the Employee type named “Employee1” and
“Employee2”. “Employee1” returns a collection of
Employees (those who report to any current
Employee); whereas “Employee2” returns a single
Employee (the current Employee‟s manager). All
very confusing!

You could, at this point, do considerable further work on your Entity Data Model. For example, you might:

 rename entities, entity properties, and entity sets, to fix pluralization, or for other reasons;
 create new entities;

49 | P a g e
IdeaBlade DevForce Hello DevForce

 describe inheritance relationships among the entities;


 create new associations (relationships) between the entities;
 define complex types within the entities;
 create and map function imports for stored procedures;
 map entities to stored procedures for CRUD (Create, Retrieve, Update, Delete) operations; and
 do other operations.

But let‟s leave it alone for now, and keep moving.


Note that we could also have included Stored Procedures and Views in our Entity Data Model while
creating it with the wizard. We didn‟t in order to keep things simple for the purpose of this beginning
tutorial. But DevForce supports everything you can do in your Entity Data Model.

Build Your Domain Model Using the DevForce Object Mapper


Now that we have an Entity Data Model to work with, we‟ll create our Domain Model.

1. From the Visual Studio main menu, select Tools / DevForce Object Mapper. DevForce, finding no domain
model already in the solution, displays a node named (New Model) in the navigational tree control.

2. Select the (New Model) node in the tree control. Observe the Project Settings:

DevForce has picked a project as the target for the generated domain model, but let‟s say we want to put the
generated DevForce class files into their own project. Click the <New project…> button. The following dialog
displays:

50 | P a g e
IdeaBlade DevForce Hello DevForce

We‟ll accept the defaults here, letting the Object Mapper create a new Windows Class Library project named
“DomainModel”. A directory of the same name will be created at the Location shown to house the generated
files.

Note also the checkbox labelled “Create Silverlight Domain Model project”:

This checkbox will only appear if you have a DevForce Universal or DevForce Silverlight license (but not a
DevForce WinClient license). In Universal it will default to being unchecked, as shown. In DevForce
Silverlight, it will default to being checked. For now we‟ll leave it as is, directing the Object Mapper not to
generate any Silverlight files.

3. Observe the “Domain Model Settings”.

When the Object Mapper saves the domain model and generates code, it will use the Namespace
(“DomainModel”) shown for the generated code, and will also name the EntityManager container
(“DomainModelEntityManager”) as shown. You‟ll use the EntityManager for retrieving data into your local
cache, for saving changes, and for many other data persistence operations.

51 | P a g e
IdeaBlade DevForce Hello DevForce

4. Now observe the “Save-Related Settings”.

By default, the Object Mapper will, when you first save your work, do all of the following:

 Generate a code file,


<DomainModel Name>.<Entity Data Model Name>.Designer.cs (or .vb);
e.g., “DomainModel.ServerModelNorthwindIB.Designer.cs”. This code file contains partial classes for
all business entities defined in the DomainModel.

 Create an app.config file in the domain model project, and store configuration information (such as
connection strings) there.

 Generate a handler for the post-build event of any executable project that references the DomainModel
assembly. This handler will make sure that

 the ideaBlade.configuration section of the app.config in that project gets updated at build time
to reflect any new information in the copy of app.config that resides in the domain model
project; and

 the assembly containing the Entity Data Model gets copied to the executables folder. 3

All .NET code will be generated in the language you select, C# or VB.

If the “Create developer classes” checkbox is checked, the Object Mapper will also generate developer partial
classes for each of the entities in your model. We‟ll discuss these more later.

6. From the (right-click) shortcut menu associated


with the domain model (New Model) node in the
tree control -- or from the Model option on the
main menu -- select Add Entity Model.

3
If the developer has elected to have the .SSDL, .CSDL, and.MSL files generated by Visual Studio from the Entity Data
Model stored as loose files rather than embedded resources, those will also get copied to the executables folder.

52 | P a g e
IdeaBlade DevForce Hello DevForce

DevForce will find the Entity Data Model in your solution and suggest that as the model to add:

Click <Open> to affirm that selection.

DevForce will display a node for the ServerModelNorthwindIB Entity Data Model as a child of the (New
Model) domain model.

You can add as many Entity Data Models as you


like to your domain model.

7. Select the ServerModelNorthwindIB.edmx node in the tree control. Observe the settings in the details pane.

53 | P a g e
IdeaBlade DevForce Hello DevForce

The meanings and uses of these settings are described in detail in the “Object Mapping” topic document in the
DevForce Learning Resources. Please refer to that for details. For our purposes here, just accept all the default
settings.

8. We‟re going to do some further work on our model, but before we do, let‟s save our work. Do so by clicking the
“Save Domain Model” button on the toolbar (with a diskette icon), or by clicking the Save option on the File
menu, or by clicking the Save option on the shortcut menu associated with the (New Model) node.
A new project named for your DomainModel is created in the current solution. In that project your domain
model is generated. Both the project file and the domain model files have been placed in a directory to house
them.
The Location requested is for the project directory. By default that directory will be created immediately within
the directory where the existing solution resides.
In addition to the above, the Object Mapper created a
Solution Folder and moved the Domain Model project
into it. You may, if you like, choose to move the
project containing the Entity Data Model into that
folder as well; but that‟s up to you.
The Object Mapper has also generated a “designer”
code file,
DomainModel.ServerModelNorthwindIB.Designer.cs,
and placed it subordinate to DomainModel.ibedmx, the
XML file representing the domain model.
DomainModel.ServerModelNorthwindIB.Designer.cs
plays a role relative to the domain model similar to that
played by the ServerModelNorthwindIB.Designer.cs
file in relation to the entity data model,
ServerModelNorthwindIB.edmx. That is, it contains the
generated C# or VB code that represents the blueprint
for the runtime object model.

Note that DomainModel. ServerModelNorthwindIB.Designer.cs contains the domain model (consisting of


DevForce entities and related classes), whereas ServerModelNorthwindIB.Designer.cs contains the entity
data model (consisting of ADO.NET EntityObjects and related classes).

If we had checked the “Create developer classes”


checkbox (see at right), then the Object Mapper
would also have generated one additional file for
each entity in our model. Each file would contain a
single partial class for the entity specified in the
file name. Let‟s do that now to see the additional
files.

If you closed the Object Mapper, re-open it. Check the “Create developer classes” checkbox and then save the
domain model again.

54 | P a g e
IdeaBlade DevForce Hello DevForce

The Object Mapper has now generated “developer” partial


classes (in individual files) for each of the types in the
business object model. These are Customer.cs, Employee.cs,
Order.cs, and so forth. Once generated, these classes won‟t
be overwritten by the Object Mapper. They‟re designed for
your custom code.

The Object Mapper also generated into the DomainModel project an app.config file that contains (among other
things) connection information for the data sources for all Entity Models contained in the Domain Model. In an
n-tier app, this connection information will not be deployed to the client machine, but only to the machine with
the DevForce Business Object Server (BOS).

Examining and Editing the Contents of the Entity Data Model in the Object Mapper

9. Reopen the Object Mapper if it is closed, and double-click the ServerModelNorthwindIB.edmx node in the tree
control, to expand it. You should see (hierarchically just below it) the container node for the
ServerModelNorthwindIB Entity Data Model, called ServerModelNorthwindIBContext. Note that this container
has the name that you specified, while creating the Entity Data Model in the EDM wizard, for saving the
connection information.

10. In the upper part of the details pane you should see the names of each type that you selected for inclusion in
ServerModelNorthwindIB. Note that both the type names and the corresponding Entity Set Names are the same
as the names of the tables on which they were based. But really, we‟d like the type names to be singular and the
Entity Set Names to be plural (e.g., the Customer type lives in the Customers entity set). We‟ll address that
presently.

11. In the lower part of the details pane see the property details for the type selected in the upper part. Select the
Order type in the upper partition of the details pane. Scroll through and note the many pieces of information
available about the Order object‟s properties that are visible or modifiable on the Simple Properties tab.

12. Select the Associations tab and note the associations discovered in the Entity Data Model. (These, in turn, were
created there because of the discovery of foreign key relations in the NorthwindIB database.)

55 | P a g e
IdeaBlade DevForce Hello DevForce

We see associations between Order and Customer, Order and Employee, and Order-OrderDetails.

13. Select the Navigation Properties tab. These properties were discovered in the Entity Data Model, where they
were generated as a consequence of the discovered relationships among the database tables. The Entity Data
Model designer named the navigation properties according to the name of their source table. But again, we
might well be a bit uncomfortable with the generated names, and wish that navigation properties that return a
collection had plural names, and ones that return a single object had singular names.

14. We‟ve already noted the navigation properties on the Employee entity (which arose from the Employee table‟s
relationship to itself), and their confusing names. We could rename those properties now, but first let‟s address
the pluralization issues in the names in the model in a global way.

15. In the tree control pane, select the ServerModelNorthwindIB.edmx node. Click the <Name Pluralizer> button in
the Entity Model Settings section. That launches the following model dialog:

16. Examine the default settings. By default, this tool will make Entity Set and Collection Navigation Property
names plural, but will make Entity Type names and Scalar Navigation Property names singular, regardless
of what they are now. Accept the default settings and click OK.

56 | P a g e
IdeaBlade DevForce Hello DevForce

17. Select the ServerModelNorthwindIBContext node in the tree control again and observe that the Entity Set
Names are now plural. Look at the Navigation Properties for the Order type and observe that the property
for OrderDetails is now named in the plural, whereas the others, which do not return collections, have been
left singular.

18. Examine the navigation properties for the


Employee entity. Running the Pluralizer changed
them from “Employee1” and “Employee2” to
“Employee1s” and “Employee2”. Now it‟s easy
to see which one returns the collection, and which
returns a scalar. Rename “Employee1s” to
“DirectReports” and “Employee2” to “Manager”.

19. Let‟s make one more manual name change. On the Simple Properties tab, change the name of the “Freight”
property to “FreightCost”. (Press ENTER to complete the change.) You can change the name of any property,
or any entity.

20. Save the Domain Model. Whenever you save again after having done so previously, the following things
happen:

a. The code in the file

DomainModel.ServerModelNorthwindIB.Designer.cs

gets overwritten;

57 | P a g e
IdeaBlade DevForce Hello DevForce

b. The individual files containing the partial “developer” classes are left alone; and,

c. Depending upon the nature of your changes, either or both of the DomainModel.ibedmx and
app.config files may be updated.

Build and Add a Second Entity Model to Your Domain Model


[If you already know that your domain model will only get data from a single datasource, or you just want a more
streamlined introduction to DevForce, feel free to skip this section. If you’re following along in Visual Studio, note
that the following discussion provides less step-by-step detail than the previous one.]
1. Now add a second Entity Model to the existing Domain Model. Our second Entity Model is based on the
Adventureworks2000 database from Microsoft, but uses only the tables Address, CountryRegion, Department,
Employee, and StateProvince.4 We‟ll name it ServerModelAw2000.

Our ServerModelAw2000 model looks something like the following when viewed in the Visual Studio Entity
Model Designer:

2. Note that the Employee entity for Adventureworks has an association to itself, just as the Employee entity
in NorthwindIB did. So it naturally has the same naming issues that we saw in that model for the navigation
properties that result from the recursive relationship. We‟ll fix them in the same way!

4
Note that the selection of Adventureworks2000 as the data source for our second Entity Data Model example was driven
much more by its likely familiarity and availability to you, the reader, than by any actual relevance it has as an extension to
NorthwindIB in a practical domain model. A more likely real-world scenario would be (as an example) one in which product
inventory information resides in one database and accounting information in another, with both types of information being
required by a target application.

58 | P a g e
IdeaBlade DevForce Hello DevForce

3. Select any other entity in the diagram and inspect its properties. Each Entity Set Name has been defaulted
to the same name as that used for the entity (e.g., the Entity Set for the Address type is named “Address”).

Don‟t do anything about the names at this time: we can address them more easily in the DevForce Object
Mapper (as we‟ve already seen).
Close the Entity Data Model based on the AdventureWorks2000 database.

Add the Adventureworks Entity Model to the DevForce Domain Model

4. From the Visual Studio menu, select Tools / DevForce Object Mapper. The Object Mapper will launch
with the existing domain model loaded.

5. Right-click the DomainModel.ibedmx node and select “Add Entity Model” from the shortcut menu. In the
File Open dialog, navigate to the ServerModelAw2000.edmx entity model, as necessary. When you open it,
you‟ll see the following message:

59 | P a g e
IdeaBlade DevForce Hello DevForce

This message results from the fact that the new Entity Model contains a type, “Employee”, whose name is
already being used in the domain model. (Remember, the ServerModelNorthwindIB model also contains
an Employee type.)
Respond by clicking the <Yes> button to allow the Object Mapper to rename the incoming class to resolve the
conflict.

6. Find and select the ServerModelAw2000Context node in the Object Mapper and inspect the imported
model. Note the following:

a. The Employee from AdventureWorks was renamed to “Employee1” to resolve the conflict with the
Employee from NorthwindEF.

b. All Entity Set Names are singular, as currently defined in the Entity Model (.edmx) file.

7. Double-click the Employee1 type in the upper right pane of the Object Mapper (titled
“ServerModelAw2000Context”) and rename it to “EmployeeAw2000”.

8. Note that all of these names are maintained in the Entity Model (.edmx) file, so changes will be written to
that file, and will be recognized by the Visual Studio Entity Data Model Designer.

9. In the tree control, re-select the “ServerModelAw2000.edmx” node. In the Entity Model Settings pane,
click the <Name Pluralizer> button. You‟ll see a dialog like the one you saw previously:

And again, if you accept all of the default, the Object Mapper will change

 Entity Sets names so that they are plural


 Entity Type names so that they are singular
 Navigation properties that return a single object so that they are singular

60 | P a g e
IdeaBlade DevForce Hello DevForce

 Navigation properties that return a collection of objects so that they are plural.

Click <OK> to accept the defaults and allow the Object Mapper to fix up names in your model.

10. Perform the same fixup on the navigation properties for the EmployeeAw2000 entity that you did for the
Employee entity in the NorthwindIB model. Rename the “Employee1s” property to “DirectReports”;
rename the “Employee2” property to “Manager”.

11. Again select the ServerModelAw2000Context node in the tree control. Note the new Entity Set Names. All
are plural and look good except the name for the EmployeeAw2000 type. It was left unchanged because the
Object Mapper noticed that the type name and EntitySet name were different to begin with. Double-click
the Entity Set Name “Employees1” and change it to “EmployeesAw2000”.

Save the Enhanced Domain Model

12. Select File / Save from the Object Mapper menu. The Object Mapper generates a second “designer” class
file under the DomainModel.ibedmx file for the new AdventureWorks2000 entities.

Since the “Create developer classes” checkbox is still checked, it also generates developer partial classes
for the new entities: Address, CountryRegion, Department, EmployeeAw2000, and StateProvince. All
entities from both data sources are now part of the same domain model.

61 | P a g e
IdeaBlade DevForce Hello DevForce

...and your solution tree should


something like that shown at right.
Note that we‟ve moved both the
ServerModelNorthwindIB and
ServerModelAw2000 projects into
the DomainModel Folder so that all
parts of our business model are
there. The organization of the
various model projects under the
“DomainModel Folder” solution
folder is, of course, a matter of
preference and not a necessity. It
does, however, make it easy to
collapse the model and all of its
parts in the solution tree when you
want to concentrate on the front-end
(or some other) project.

Add Business Logic


Since we elected to generate “developer partial classes” for each entity in our Domain Model, those entities are each
now represented by two partial classes – the elective one (stored in a stand-alone file, e.g., Employee.cs), and one in
the “designer” code file associated with the ibedmx (e.g., DomainModel.ServerModelNorthwindIB.Designer.cs.)
The partial class in the designer file is subject to frequent regeneration by the Object Mapper, and as such isn‟t the
place to put custom logic – or to make any sort of manual changes
The partial class in the stand-alone file, on the other hand, is expressly designed as the venue for such customization.
Let‟s add some custom logic to the Employee partial class in Employee.cs.

62 | P a g e
IdeaBlade DevForce Hello DevForce

1. View the code in Employee.cs (or .vb), and find the Suggested Customizations region. Just before the
#endregion statement (#End Region in VB), add the following code:

C# /// <summary>
/// Age as of today
/// </summary>
public int Age {
get {
if (null == this.BirthDate) return 0;
DateTime oBirthDate = (DateTime)this.BirthDate;
DateTime oToday = DateTime.Today;
int oAge = oToday.Year - oBirthDate.Year;
if (oBirthDate.AddYears(oAge) > oToday) oAge--;
if (oAge < 0) return 0;
else return oAge;
}
}

VB ''' <summary>
''' Age as of today
''' </summary>
Public ReadOnly Property Age() As Integer
Get
If Nothing Is Me.BirthDate Then
Return 0
End If
Dim oBirthDate As Date = CDate(Me.BirthDate)
Dim oToday As Date = Date.Today
Dim oAge As Integer = oToday.Year - oBirthDate.Year
If oBirthDate.AddYears(oAge) > oToday Then
oAge -= 1
End If
If oAge < 0 Then
Return 0
Else
Return oAge
End If
End Get
End Property

This code defines a calculated property, Age, which returns the Employee‟s current age by calculating it from their
birthdate.

2. Below the Age property, add a second one:

C# /// <summary>
/// Total revenue for this Employee's orders
/// </summary>
public double TotalOrderRevenue {
get {
double revenue = 0;
foreach (Order aOrder in this.Orders) {
foreach (OrderDetail aOrderDetail in aOrder.OrderDetails) {
revenue += aOrderDetail.Quantity *
Convert.ToDouble(aOrderDetail.UnitPrice) * aOrderDetail.Discount;

63 | P a g e
IdeaBlade DevForce Hello DevForce

}
}
return revenue;
}
}

VB ''' <summary>
''' Total revenue for this Employee's orders
''' </summary>
Public ReadOnly Property TotalOrderRevenue() As Double
Get
Dim revenue As Double = 0
For Each aOrder As Order In Me.Orders
For Each aOrderDetail As OrderDetail In aOrder.OrderDetails
revenue += aOrderDetail.Quantity * Convert.ToDouble(aOrderDetail.UnitPrice)
* aOrderDetail.Discount
Next aOrderDetail
Next aOrder
Return revenue
End Get
End Property

This property uses navigation properties on the Employee and Order entities, automatically generated by the Entity
Framework and carried through into the DevForce entities, to roll up the revenue from each line item of each order
written by the Employee.

3. Let‟s add one more bit of custom logic to see how we can modify the behavior of even those properties that
map directly to a back-end datasource and were therefore written into the generated class in
DomainModel.ServerModelNorthwindIB.Designer.cs. We‟ll make a simple adjustment: we‟ll convert the
Employee‟s LastName value to upper-case for display purposes while allowing entered or changed values to
retain whatever capitalization the end user entered. In other words, we want a value stored as “Davolio” in the
back-end datasource to be returned as “DAVOLIO” when we ask for it from the Employee object.

First let‟s have a look at that generated code. There doesn‟t appear to be much there, but it has an amazing
amount of flexibility built into it, behind the scenes:

C# #region LastName property

/// <summary>Gets or sets the LastName. </summary>


[Bindable(true, BindingDirection.TwoWay)]
[Editable(true)]
[Display(Name="Last Name", AutoGenerateField=true)]
[IbVal.ValidateProperty]
[IbVal.StringLengthVerifier(MaxValue=30, IsRequired=true)]
[IbCore.MaxTextLength(30)]
[MsSer.DataMember]
public String LastName {
get { return LastNameEntityProperty.GetValue(this); }
[System.Diagnostics.DebuggerNonUserCode]
set { LastNameEntityProperty.SetValue(this, value); }
}
#endregion LastName property

VB #Region "LastName property"

64 | P a g e
IdeaBlade DevForce Hello DevForce

'''<summary>Gets or sets the LastName. </summary>


<Bindable(true, BindingDirection.TwoWay)> _
<Editable(true)> _
<Display(Name:="Last Name", AutoGenerateField:=true)> _
<IbVal.ValidateProperty> _
<IbVal.StringLengthVerifier(MaxValue:=30, IsRequired:=true)> _
<IbCore.MaxTextLength(30)> _
<MsSer.DataMember> _
Public Property LastName() As String
Get
Return LastNameEntityProperty.GetValue(Me)
End Get
<System.Diagnostics.DebuggerNonUserCode> _
Set
LastNameEntityProperty.SetValue(Me, value)
End Set
End Property
#End Region

An important part of this flexibility is provided by DevForce‟s support for methods known as property
interceptors. Let‟s implement one now.

4. Enter the following code below the TotalRevenueCost property that you most recently added:

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

UppercaseLastName() simply converts the current value of the LastName property (passed to the method in
args.Value) as desired, and the work is done.
The [AfterGet] attribute with which the public method UppercaseLastName() is decorated tells DevForce to call that
method during any get operation for the designated property, just after the raw value is retrieved from the local
instance of the object. The static5 EntityPropertyNames.LastName property, included in the attribute, simply returns
the string-valued name of the LastName property. (The EntityPropertyNames class was automatically created by
DevForce during Object Mapper code generation so you don‟t have to hard-code property names and risk
misspelling them, in this and other contexts.)
You can call the method anything you like (since the [AfterGet] attribute defines its role), but the signature does
requires the args parameter, which is an instance of IPropertyInterceptorArgs. The version used here employs the
generic version of IPropertyInterceptor, which fully specifies the type of both the property and its containing entity,

5
“Shared” in VB

65 | P a g e
IdeaBlade DevForce Hello DevForce

so that you need not cast Args.Value within the method code. The compiler already knows (in this example) that it‟s
a string.

Add a User Interface


Let‟s implement a quick console app to use as a front-end for our application so we can see the results of our custom
work on the Employee class. We‟re choosing a console app as a UI, for now, simply for its simplicity. Later in this
example we add a Winforms UI; and then a Silverlight UI.
A WPF UI is yet another available option. Examples of all four UI types are included in the Learning Units that
install with DevForce.

1. Compile your DomainModel project.


2. Add a new project, a Console Application, naming it “Console01”.
3. To Console01, add references to IdeaBlade.EntityModel and to the DomainModel project.
4. Add the statements shown in bold red below to the Main() method in Program.cs so that it looks as follows:

C# using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using IdeaBlade.EntityModel;
using DomainModel;

namespace Console01 {
class Program {
static void Main(string[] args) {
GetEmployees();
}

private static void GetEmployees() {


var query = _manager.Employees;
foreach (Employee anEmployee in query) {
Console.WriteLine("Last Name = " + anEmployee.LastName);
Console.WriteLine("\tBirth date = " + anEmployee.BirthDate.ToString());
Console.WriteLine("\tAge = " + anEmployee.Age);
Console.WriteLine(String.Format("\tTotal Order Revenue: {0:C}",
anEmployee.TotalOrderRevenue));
Console.WriteLine();
}
PromptToContinue();
}

private static void PromptToContinue() {


Console.WriteLine();
Console.WriteLine("Press ENTER to continue...");
Console.ReadLine();
}

#region Private Fields


static DomainModelEntityManager _manager =
DomainModelEntityManager.DefaultManager;
#endregion
}

66 | P a g e
IdeaBlade DevForce Hello DevForce

VB Imports IdeaBlade.EntityModel
Imports DomainModel

Module Module1

Sub Main()
GetEmployees()
End Sub

Private Sub GetEmployees()


Dim query = _manager.Employees
For Each anEmployee As Employee In query
Console.WriteLine("Last Name = " & anEmployee.LastName)
Console.WriteLine(vbTab & "Birth date = " & anEmployee.BirthDate.ToString())
Console.WriteLine(vbTab & "Age = " & anEmployee.Age)
Console.WriteLine(String.Format(vbTab & "Total Order Revenue: {0:C}",
anEmployee.TotalOrderRevenue))
Console.WriteLine()
Next anEmployee
PromptToContinue()
End Sub

Private Sub PromptToContinue()


Console.WriteLine()
Console.WriteLine("Press ENTER to continue...")
Console.ReadLine()
End Sub

#Region "Private Fields"


Private _manager As DomainModelEntityManager = DomainModelEntityManager.DefaultManager
#End Region

End Module

5. Make Console01 the Startup Project for your DevForce01 solution. Compile and then run the app. You should
see output similar to the following:

67 | P a g e
IdeaBlade DevForce Hello DevForce

Note that, for each Employee, the


LastName is being converted to upper
case; the Age is being computed
properly from the birth date; and the
Total Order Revenue is being rolled up
across all Orders and their line items!

6. Press the ENTER key to end the app.

Add Unit Tests


We sheepishly dodged the “Test First!” methodology. But we‟re not going to skip the tests altogether. We‟re going
to lay them in right now.
Do write unit tests as you go!

Unit Testing with Visual Studio Team Test

Do you have Team Test? Look for “Test” among the Visual Studio menus. If it‟s there, you‟ve got
Team Test; if it isn‟t, you don‟t. For now you should skip ahead.
Before you do, think about how you will test your application. If you can‟t afford Team Test, you
might consider NUnit. It‟s solid and it‟s free. While it won‟t generate the template tests (we‟ll see
Team Test do that shortly), it is otherwise remarkably similar to testing with Team Test.

It‟s easy to get started with the new Visual Studio Team Test.
1. Open Employee.cs (or .vb) in code view, and right-click in the Code View window.
2. Pick “Create Unit Tests …” The “Create Unit Tests” Dialog appears.

68 | P a g e
IdeaBlade DevForce Hello DevForce

3. Un-check the DomainModel assembly.


4. Expand the DomainModel.Employee node, and select the following items for testing:
 Employee() (the parameterless constructor)
 Age (one of our calculated properties)

5. For “Output Project”, accept “Create a new Visual C# [or VB] Test Project”...

6. Click [OK]
7. Enter “DomainModel.Test” as the project name, when asked, and click <Create>.
After some grinding in the background, you will see the DomainModel.Test project added to the solution, with
references to the DomainModel and the necessary IdeaBlade assemblies. You will also see an EmployeeTest.cs (or
.vb) tab and an AuthoringTests.txt tab. AuthoringTests is a non-functional introduction to testing. It‟s well worth
reading at some point, but for now, just close it.

69 | P a g e
IdeaBlade DevForce Hello DevForce

Configure the Test Project


We‟re almost ready to run our test, but we have just a couple of configuration changes to make the test project ready
to run.
1. Make sure the following using [C#] or Imports [VB] statements at the top of the Employee.Test.cs (or .vb) file:

C# using DomainModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;
using IdeaBlade.Core;

VB Imports DomainModel
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports System.Collections.Generic
Imports System.Linq
Imports IdeaBlade.Core

2. Build or rebuild the entire solution.


3. Set LocalTestRun.testrunconfig to deploy the app.config to the test output directory. You can do this as
follows:

a. On the Visual Studio Test menu, select Edit Test Run Configurations and then Local Test Run
(localtestrun.testrunconfig).

b. On the localtestrun.testrunconfig dialog, select Deployment.

c. Click <Add File...>. Change the Objects of type select to “All files(*.*)”, and find the app.config
file in the folder for the solution‟s executable project (DevForce01\Console01), select it, and click
the <Open> button. The localtestrun.testrunconfig dialog should then appear as follows:

70 | P a g e
IdeaBlade DevForce Hello DevForce

Note that there is no need to set the ConfigFileLocation in code (e.g., in one of the test initializer methods),
because, as in any DevForce app, DevForce automatically searches the main application folder
(BaseAppDirectory) for an app.config file. It will therefore find the one deployed in the test deployment folder
(TestDeploymentDir).6

4. Next we need to ensure that the server model assembly will be deployed to the test result directory. Click <Add
File...> again. Navigate to the bin\debug folder under the project folder for your Entity Data Model project (the
ServerModelNorthwindIB folder, in our case). Select the ServerModelNorthwindIB assembly:

6
You can find detailed information about how DevForce finds configuration in the appendix to this chapter entitled “Probing
Sequence for the App.Config File”.

71 | P a g e
IdeaBlade DevForce Hello DevForce

5. If you have elected to have the Entity Data Model artifacts stored as loose files rather than to be embedded in
the server model assembly (as is the default), then you will also need to add the three components of the
ServerModelNorthwindIB model (the .ssdl, .csdl, and .msl files). You will find these in the same folder where
you found the model assembly.

6. On the localtestrun.testrunconfig dialog, click <Close> and respond Yes when prompted whether to save
changes to the testrunconfig.

EmployeeTest First Look


The action is in EmployeeTest.cs (or .vb). It is a little forbidding at first but we‟ll clear that up in a few quick steps.
1. Ignore the TestContext property (perhaps by wrapping in its own region).
2. Close the “Additional Test Attributes” region if it‟s open – we don‟t need it yet.
3. Find the EmployeeConstructorTest (). Rework it to look like the following:

C# /// <summary>
///A test for Employee Constructor
///</summary>
[TestMethod()]
public void EmployeeConstructorTest() {
Employee target = new Employee();
string desiredTypeName = "Employee";
string actualTypeName = target.GetType().Name;
Assert.IsTrue(actualTypeName == desiredTypeName,
"Created type, '" + actualTypeName + "', is incorrect. It should be '" +
desiredTypeName + "'.");
}

VB ''' <summary>

72 | P a g e
IdeaBlade DevForce Hello DevForce

'''A test for Employee Constructor


'''</summary>
<TestMethod()> _
Public Sub EmployeeConstructorTest()
Dim target As New Employee()
Dim desiredTypeName As String = "Employee"
Dim actualTypeName As String = target.GetType().Name
Assert.IsTrue(actualTypeName = desiredTypeName, "Created type, '" & actualTypeName & "',
is incorrect. It should be '" & desiredTypeName & "'.")
End Sub

That leaves just the Age property that we added. The following test method is automatically generated for that:

C# /// <summary>
///A test for Age
///</summary>
[TestMethod()]
public void AgeTest() {
Employee target = new Employee(); // TODO: Initialize to an appropriate value
int actual;
actual = target.Age;
Assert.Inconclusive("Verify the correctness of this test method.");
}

VB '''<summary>
'''A test for Age
'''</summary>
<TestMethod()> _
Public Sub AgeTest()
Dim target As Employee = New Employee ' TODO: Initialize to an appropriate value
Dim actual As Integer
actual = target.Age
Assert.Inconclusive("Verify the correctness of this test method.")
End Sub

The newly created Employee won’t have an Age property value that’s of much interest. Let’s fix up the test so it
uses an actual employee in our test database.

Get a Test Employee


We will want to be careful to keep the test data always in a known state. If we make changes, we‟d better restore the
original data even if our tests fail. We should have a back up of the database just in case.
One of the cardinal testing rules is that there should be no dependencies among the tests. That means that tests can
be run independently and in any order. Accordingly, if we make changes during a test, we must restore the original
data immediately after the test. We must make sure we do so even if the test fails.
Do restore the test data source after every test
Do maintain a back up of the database just in case.

73 | P a g e
IdeaBlade DevForce Hello DevForce

Our example uses the NorthwindIB database as its data source. It has well-known data and we‟re not going to make
any changes just yet, so we‟re fine.
1. We‟re not going to be purists now – this is just a tutorial – so we‟ll be sloppy with our first test. Let‟s rewrite it
so it looks like this:

C# /// <summary>
///A test for Age
///</summary>
[TestMethod()]
public void AgeTest() {
DomainModelEntityManager manager =
DomainModelEntityManager.DefaultManager;
Employee target = manager.Employees
.Where(e => e.LastName.ToLower() ==
"Davolio".ToLower()).ToList().First<Employee>();
int actual = target.Age;
int approxAge = System.DateTime.Now.Year - target.BirthDate.Value.Year;
Assert.IsTrue(approxAge - actual <= 1);}

VB ''' <summary>
'''A test for Age
'''</summary>
<TestMethod()> _
Public Sub AgeTest()
Dim manager As DomainModelEntityManager = DomainModelEntityManager.DefaultManager
Dim target As Employee = manager.Employees _
.Where(Function(e) e.LastName.ToLower() = "Davolio".ToLower()).ToList().First()
Dim actual As Integer = target.Age
Dim approxAge As Integer = Date.Now.Year - target.BirthDate.Value.Year
Assert.IsTrue(approxAge - actual <= 1)
End Sub

Run the Test


1. Open the Test View from the menu Test ► Windows ► Test View.
2. Run the selected AgeTest test by selecting it and clicking the <Run
Selection> button, as shown at right.

A “Test Results” window appears at the bottom of Visual Studio. The test passes!

74 | P a g e
IdeaBlade DevForce Hello DevForce

Just FYI, in case you‟re new to Team Test: you can also run the tests in debug mode so you can insert breakpoints.
Instead of starting the test with the <Run Selection> button, to run in debug mode, right-click the test and select
Debug Selection.

Accumulating Test Results


Team Test keeps track of every test run. You can review them from within the “Test Results” window shown
immediately above.

When you examine your solution directory in Windows Explorer, you will find the TestResults directory where
these results are stored. You don‟t want this directory under source control and you definitely want to clear it out
periodically. Prune or delete at your leisure.

Lessons Learned
We have the foundation for testing the logic we add to our business entities. It didn‟t take long to set up a test
environment. Now it‟s just a matter of keeping it up.
There is far more to learn about testing than we can cover in this Guide. Check the “Suggested Reading” chapter in
the Concepts Manual for our recommendations. There is no end to the information available on the web.

Add a WinForm UI
In Visual Studio 2008, you have many options for a UI. You can use Winforms, WebForms, WPF, or Silverlight. If
you choose Winforms, you have the option to use the UI-related assemblies of DevForce to grease the wheels.
We‟ll see what that looks like now.

Add a Windows Forms Application Project

1. In the Solution Explorer, right-click the solution node and select a new Windows Forms Application. Name
the project “WinForms01”.

2. Set a reference in the WinForms01 project to the DomainModel project so our form can see the entities in
the domain model. Set references to IdeaBlade.Core and IdeaBlade.EntityModel and so you can use the
DevForce types you‟ll need. Finally, set a reference to the .NET assembly WindowsBase, as it contains an
interface used by the DevForce BindableList<T> type that you‟ll use in data binding.

75 | P a g e
IdeaBlade DevForce Hello DevForce

3. In the designer, select Form1. In the Properties window, change the Text property of the Form from
“Form1” to “Employees”. In the Solution Explorer, rename the file containing the form from “Form1.cs”
(or .vb) to “EmployeeForm.cs” (or .vb). Say yes when asked whether you want to rename references to the
form in the project:

4. Find the BindingSource control in the Toolbox (“All Windows Forms” group) and drag two of them on to
the form. Name one of them “_ordersBindingSource” and the other “_employeesBindingSource”. (We‟re
using the underscore prefix “_” for elements that will be scoped at the class level in our form, as these
will.)

5. Drag a BindingNavigator from the ToolBox to the form, positioning it along the top edge. Name that
“_employeesBindingNavigator”.

6. Find the “IdeaBlade DevForce” group in the Toolbox. (Remember, it won‟t be there unless you installed
the DevForce WinForms UI support when you installed DevForce!) Drag a ControlBindingManager and
then a DataGridViewBindingManager on to the form. Rename the ControlBindingManager
“_employeesControlBindingManager”; rename the DataGridViewBindingManager
“_ordersDataGridViewBindingManager”.

7. In the component tray, select the _employeesControlBindingManager. Then, in the Properties window,
assign the _employeesBindingSource to the BindingSource property for the
_employeesControlBindingManager.

76 | P a g e
IdeaBlade DevForce Hello DevForce

8. Similarly, assign the _ordersBindingSource to the BindingSource property for the


_ordersDataGridViewBindingManager.

9. In the component tray, select the _employeesControlBindingManager. Next, right-click the smart tag that
shows up at its upper right corner, and choose Autopopulate Controls. In the dialog “Bind to which object
type?”, select the DomainModel assembly and the Employee type, then click <Ok>. You‟ll see the
DevForce “Configure Databindings” designer:

10. From the Properties tree control, drag the following properties onto the Autopopulation tab: LastName,
FirstName, BirthDate, Age, and Photo.

11. Click the Naming Conventions button and add an underscore in front of the default text of “{0}{1}” to
make it “_{0}{1}”. Click the <Update Sample> button if you want to see what it will look like. Then click
<OK>.

77 | P a g e
IdeaBlade DevForce Hello DevForce

12. On the “Configure DataBindings” dialog, click <OK> to close it and autopopulate the form with controls
for your selected properties. Rearrange the controls as desired. We‟ll move the PictureBox for the Photo to
the right of the other controls, and delete its label.

13. Now select the _ordersDataGridViewBindingManager, click the Smart Tag, select “Configure
Databindings”, select DomainModel as the assembly containing your target types, and select Order as the
target type. Into the grid, drag the properties OrderDate, RequiredDate, ShippedDate, and FreightCost.

14. Expand the Customer property in the tree control and drag the CompanyName property of the Customer
onto the grid. Drag it upward to make it the top row. Edit the value in the “Column Title” column from
“Customer Company Name” to “Company”.

15. Click <OK> to close the dialog. Whoops! The designer warns you that you haven‟t linked your selected
properties to a grid (see picture below). Response that <No>, you don‟t want to exit just yet.

16. Click the <Create Grid> button, and name the grid to be created “_ordersDataGridView”. Click <OK>,
then click <OK> on the main dialog again. (If you get prompted asking whether to enlarge the form to
accommodate the new control, say yes.) The designer configures a DataGridView control and plops it on to
your form. Position and size it as desired. Your form should now look something like the following:

78 | P a g e
IdeaBlade DevForce Hello DevForce

17. There‟s more configuration we could do in the designer, but we‟ll choose to do it in the “code behind” for
our form, instead. Make that look as follows:

C# using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using IdeaBlade.EntityModel;
using IdeaBlade.Util;
using DomainModel;

namespace WinForms01 {
public partial class EmployeeForm : Form {
public EmployeeForm() {
InitializeComponent();
this.Load+=new EventHandler(EmployeeForm_Load);
}

private void EmployeeForm_Load(object sender, EventArgs e) {


ConfigureBindingSources();
ConfigureBindingNavigators();
ConfigureBindingManagers();
ConfigureHandlers();
LoadData();
}

private void ConfigureBindingSources() {


_employeesBindingSource.DataSource = _employees;

79 | P a g e
IdeaBlade DevForce Hello DevForce

_ordersBindingSource.DataSource = _orders;
}

private void ConfigureBindingNavigators() {


_employeesBindingNavigator.BindingSource = _employeesBindingSource;
}

private void ConfigureBindingManagers() {


_employeesControlBindingManager.BindingSource = _employeesBindingSource;
_ordersDataGridViewBindingManager.BindingSource = _ordersBindingSource;
}

private void ConfigureHandlers() {


_employeesBindingSource.CurrentChanged += new
EventHandler(_employeesBindingSource_CurrentChanged);
}

void _employeesBindingSource_CurrentChanged(object sender, EventArgs e) {


Employee currentEmployee = (Employee)_employeesBindingSource.Current;
_orders.ReplaceRange(currentEmployee.Orders);
}

private void LoadData() {


_employees.ReplaceRange(_manager.Employees);
}

#region Private Fields


DomainModelEntityManager _manager =
DomainModelEntityManager.DefaultManager;
BindableList<Employee> _employees = new BindableList<Employee>();
BindableList<Order> _orders = new BindableList<Order>();
#endregion
}
}

VB Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.Linq
Imports System.Text
Imports System.Windows.Forms

Imports IdeaBlade.EntityModel
Imports IdeaBlade.Util
Imports DomainModel

Partial Public Class EmployeeForm


Inherits Form
Public Sub New()
InitializeComponent()
AddHandler Load, AddressOf EmployeeForm_Load
End Sub

Private Sub EmployeeForm_Load(ByVal sender As Object, ByVal e As EventArgs)


ConfigureBindingSources()
ConfigureBindingNavigators()
ConfigureBindingManagers()
ConfigureHandlers()

80 | P a g e
IdeaBlade DevForce Hello DevForce

LoadData()
End Sub

Private Sub ConfigureBindingSources()


_employeesBindingSource.DataSource = _employees
_ordersBindingSource.DataSource = _orders
End Sub

Private Sub ConfigureBindingNavigators()


_employeesBindingNavigator.BindingSource = _employeesBindingSource
End Sub

Private Sub ConfigureBindingManagers()


_employeesControlBindingManager.BindingSource = _employeesBindingSource
_ordersDataGridViewBindingManager.BindingSource = _ordersBindingSource
End Sub

Private Sub ConfigureHandlers()


AddHandler _employeesBindingSource.CurrentChanged, AddressOf
_employeesBindingSource_CurrentChanged
End Sub

Private Sub _employeesBindingSource_CurrentChanged(ByVal sender As Object, ByVal e As


EventArgs)
Dim currentEmployee As Employee = CType(_employeesBindingSource.Current, Employee)
_orders.ReplaceRange(currentEmployee.Orders)
End Sub

Private Sub LoadData()


_employees.ReplaceRange(_manager.Employees)
End Sub

#Region "Private Fields"


Private _manager As DomainModelEntityManager = DomainModelEntityManager.DefaultManager
Private _employees As New BindableList(Of Employee)()
Private _orders As New BindableList(Of Order)()
#End Region

End Class

Make “WinForms01” the start-up Project


18. In the Solution Explorer, right-click the Winforms01 project node and select Set As Startup Project.

Run It!
19. Build and run your app.

81 | P a g e
IdeaBlade DevForce Hello DevForce

Please note: The code solutions that accompany this document in the Learning Resources reflect the work
to this point, with the WinForm user interface.

Add a WPF UI
Please see the Learning Units that install with DevForce for examples of a DevForce app with a WPF user interface.

Create a Silverlight App with DevForce

Features described in the section are included with the DevForce Silverlight product.

Re-open the Object Mapper and check the “Generate Silverlight Projects” checkbox:

82 | P a g e
IdeaBlade DevForce Hello DevForce

Doing so causes a ComboBox for the project name to appear, and a button to create a new project. Click the <New
Project…> button to see the Create Project dialog:

83 | P a g e
IdeaBlade DevForce Hello DevForce

We‟ll accept the default name for the project, “DomainModelSL”, by clicking <OK>. Then we will again save our
Domain Model.
After closing the Object Mapper, we see that it has created a new project named “DomainModelSL”.

Now that we have our Silverlight copy of the DomainModel, it‟s time to add the executable project. .NET provides
a Silverlight Application project template, but that‟s not what you want:

84 | P a g e
IdeaBlade DevForce Hello DevForce

Why not? Because we‟ve provided a DevForce-aware template that will get you there a lot faster, and with much
less trouble.
Under the Visual C# (or VB) project types, find the DevForce section and select the “DevForce Silverlight
Application” template. We‟ll name our project “DevForceSilverlight01”:

Visual Studio creates a Silverlight project of the specified name. It also creates a web project to host the Silverlight
app, naming it “<appname>Web”, or in our example, “DevForceSilverlight01Web”.

85 | P a g e
IdeaBlade DevForce Hello DevForce

Actions of the DevForce Silverlight Application Project Template


The “DevForce Silverlight Application” template, like the standard Visual Studio Silverlight Application template,
creates a Silverlight application project and a web application project. But it also provides considerable DevForce-
specific functionality to make it easy to host a BOS in the same web application project.
In the web application project, it does all of the following:
 includes two WCF service files for the BOS, EntityService.svc and EntityServer.svc;
 includes a Global.asax showing how to check the IdeaBlade configuration information and to enable
support for a trace viewer;
 provides a web.config file that contains WCF ServiceModel configuration information for the BOS (the
EntityService and EntityServer services), as well as a stub ideaBlade.configuration section;
 configures the Default.aspx file so that it contains the Silverlight control, and skips the creation of
Silverlight test pages;
 creates a log folder to hold the debuglog.xml file
 includes references to necessary IdeaBlade assemblies; and
 selects the “specific port” setting and specifies 9009 as the default port. Using port 9009 is not a DevForce
requirement, but it‟s a handy open port that can be used during development; and using a specific port
rather than an auto-assigned one makes communicating with the BOS easier.
In the Silverlight application project as well, the DevForce Silverlight Application project template includes
references to needed IdeaBlade assemblies.
The assembly and namespace names for both projects are set to the same value. This is necessary if you plan to
place the Domain Model in the web application project and the linked (Silverlight) domain model in the Silverlight
application project. Using the same assembly name and namespace allows DevForce seamlessly to transmit entities
between the .NET and Silverlight environments. If your domain model will not be in these projects but in a separate

86 | P a g e
IdeaBlade DevForce Hello DevForce

assembly, then that assembly and the assembly holding the linked SL model must have the same name and
namespace.

Which Startup Project?


The DevForce Silverlight Application template, like the standard one, designates the web project as
the Startup Project. That‟s important. If the Silverlight project is designated as the Startup Project,
the application will start, and you‟ll probably see the start page; but the app, in that circumstance, is
running under the file system instead of being served by a web server. When running under the file
system it can‟t access a service such as the DevForce Business Object Server; so you will not, for
example, be able to retrieve data.
You can easily tell how your app is being hosted by looking at the browser‟s address bar. If it‟s being
served by a web server, the URL in the address bar will start with http://.

If it‟s running from the file system, the URL in the address bar will look like a file path (and you‟ll
see no evidence of retrieved data where you otherwise would have).

87 | P a g e
IdeaBlade DevForce Hello DevForce

Resuming the Walk-Through...


Launching the project, you should see the following display in your browser. (We‟ve made our default browser
Mozilla Firefox to demonstrate the browser independence of Silverlight!) The text you see is displayed in a
Silverlight TextBlock control hosted by Page.xaml in the Silverlight project. You‟re ready immediately to start
building meaningful functionality into that page – including creating data bindings to your DevForce entities. See
the Learning Units for samples of Silverlight / DevForce applications!

Specifying a Target Browser


If you have more than one Silverlight-compatible browser installed on your computer, you can specify in which
browser you would like your Silverlight app to launch.

88 | P a g e
IdeaBlade DevForce Hello DevForce

Find the start page for your app (Default.aspx as


generated by the project template ), right-click it, and
select Browse With... Then select the desired browser
and click the <Set as Default> button. You may then
click <Browse> to launch the app, or <Cancel> if all you
wanted to do was to change the setting. Either way,
subsequent launches of the application will occur in the
browser you specified.

This concludes our walk-through of the setup of a DevForce Silverlight application. To see more detailed sample
Silverlight / DevForce applications, please consult the Learning Units that install with DevForce.

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, and there is a flow of information between them. In this section we‟ll explain those
roles and information flows.

89 | P a g e
IdeaBlade DevForce Hello DevForce

The sample Visual Studio solution at right


includes three copies of app.config, one in
each of the following locations:

1. in the project for the Entity Data


Model (#1);
2. in the project for the
DomainModel (#2); and
3. in the executable project (#3)

We‟ve listed them in the above order


because it reflects the flow of information
between them. (We‟ll provide more detail
on that momentarily.)

The App.Config in the Entity Data 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 DomainModel project (#2) typically gets there by being generated by DevForce. It contains,
most importantly, an ideaBlade.configuration section with edmKeys that include connection information and other
settings related to specific data sources; and other settings that control or reflect such things as application logging
behavior, location of the Business Object Server, etc. The connection information included in the edmKeys usually
originates from the EDM project‟s app.config (#1), being copied from there by DevForce. However, the developer
can add to it manually or using the DevForce Configuration Editor. 7
The app.config file associated with the Entity Data Model enables the Entity Data Model designer to find the EDM‟s
datasource. As such, it is essential to the design-time utility of that designer. The app.config file associated with the
Domain Model is updated at design time, but other than to be available for update, its contents have no design-time
function. The same is true for the app.config associated with the executable project. The latter, however, becomes
the (only) copy of app.config used at runtime. It is actually copied and renamed by Visual Studio to reflect the name
of the assembly; for example, if the UI project above is used to create an assembly named UI.exe, then Visual Studio
will create a copy of the app.config file in that UI project and name the copy UI.exe.config. The latter is the version
used at runtime by the .NET framework.

7
“Config Editor” under IdeaBlade DevForce / Tools on the Windows Start menu.

90 | P a g e
IdeaBlade DevForce Hello DevForce

Information Flow Between the App.Configs


The typical flow of information between the copies of app.config is as follows.
1. Information is written to the EDM app.config (#1), usually by the EDM designer.

2. The DomainModel app.config (#2) is updated using information from one or more EDM app.configs. This
update occurs either upon a Save in the DevForce Object Mapper, or when the developer responds “Yes” to
the following prompt, which is presented after the developer saves a change to the Entity Data Model8:

3. The app.config associated with the executable project (#3) is updated using information from the
DomainModel app.config. This occurs at build time 9 when DevForce discovers a mismatch between the
information in the ideaBlade.configuration section of the executable project app.config and the
corresponding information in the DomainModel copy (#2). Before updating app.config #3 DevForce
prompts you as follows:

Note that, except for updating the ideaBlade.configuration and configSections elements of the executable project‟s
app.config (#3), DevForce leaves the app.config alone. Thus, it can contain any other elements and information
needed by the app; those will be left undisturbed.

8
DevForce watches the Visual Studio IDE for such a change, and responds when it occurs by presenting this prompt.
9
Specifically, when the executable project is built. This is performed by a Build Event handler attached by DevForce to the
executable project.

91 | P a g e
IdeaBlade DevForce Hello DevForce

The flow of information


between app.configs 1, 2, and 3
is summarized in the graphic at
right.
Content from the EDM
app.config (#1) flows to the
DomainModel app.config (#2).
Content from the DomainModel
app.config (#2) flows to the
executable project app.config
(#3).
There is no “back flow” of
information; and unrelated
app.config content is preserved
at each stage during updating.

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 2005.
Launch the SQL Server Management Console.
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]

92 | P a g e
IdeaBlade DevForce Hello DevForce

The trace window fills, showing us exactly how we’re hitting the database.

DevForce DebugLog
We may want to supplement the SQL Profiler with a DevForce tool that helps us see both the database activity and
the other application activity that surrounds it.
DevForce applications generate a trace log10 every time you run the application. The log appears in the executable
directory; its default name is “DebugLog.xml”11.
Open Windows Explorer.
Navigate to the ..\bin\debug directory under the UI 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
The DevForce TraceViewer affords a friendlier and more dynamic look at logged activity. 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.

10
You can turn it off or filter it.
11
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.

93 | P a g e
IdeaBlade DevForce Hello DevForce

Appendix: Listings of Sample App.Config Files


Listing 1. App.Config associated with the Entity Data 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 DomainModel

XML <?xml version="1.0" encoding="utf-8"?>


<configuration>
<configSections>
<section name="ideablade.configuration"
type="IdeaBlade.Core.Configuration.IdeaBladeSection, IdeaBlade.Core, Version=5.1.0.0,
Culture=neutral, PublicKeyToken=287b5094865421c0" />
</configSections>

<ideablade.configuration version="5.00" updateFromDomainModelConfig="Ask">


<logging logFile="DebugLog.xml" />
<objectServer isDistributed="false" remoteBaseURL="http://localhost"
serverPort="9009" serviceName="EntityService" />
<edmKeys>
<edmKey name="Default"
connection="metadata=res://ServerModelNorthwindIB/ServerModelNorthwindIB.csdl|res://Se
rverModelNorthwindIB/ServerModelNorthwindIB.ssdl|res://ServerModelNorthwindIB/ServerMo
delNorthwindIB.msl;provider=System.Data.SqlClient;provider connection
string=&quot;Data Source=.;Initial Catalog=NorthwindIB;Integrated
Security=True;MultipleActiveResultSets=True&quot;"
containerName="ServerModelNorthwindIB.ServerModelNorthwindIBContext">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelNorthwindIB" />
</probeAssemblyNames>
</edmKey>
</edmKeys>
</ideablade.configuration>
</configuration>

94 | P a g e
IdeaBlade DevForce Hello DevForce

Listing 3. Copy of app.config in the project for the executable

XML <?xml version="1.0" encoding="utf-8"?>


<configuration>
<!—The following section is left alone by DevForce when
it updates the app.config in the executable project -->
<DeveloperAddedSection>
<ArbitraryElementNo1 />
<ArbitraryElementNo2 />
</DeveloperAddedSection>

<connectionStrings>
...snip
</connectionStrings>
<configSections>
...snip
</configSections>
<ideablade.configuration version="5.00" updateFromDomainModelConfig="Ask">
…snip
</ideablade.configuration>
</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”, “web.config” or
“app.config”, in that order. Note that files matching “*.dll.config” will not be found.
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”, “web.config” or
“app.config”, again in that order. This property is read-only and determined at run time, and is usually the
directory from which the entry assembly was loaded. However, in a test project executable, which has no
entry assembly, the AppDomain.BaseDirectory is used. In MSTest, this will be the
TestContext.TestDeploymentDir.
4. DevForce next searches for an embedded resource named “app.config” in the entry assembly. This is
ignored in the case of a test project because the entry assembly is null.
Finally, DevForce will look for an embedded resource named “app.config” in an assembly named “AppHelper”.
This search is done for reason of backward compatibilility with DevForce applications that use the AppHelper
project formerly generated by the DevForce Object Mapper.

95 | P a g e
IdeaBlade DevForce Class Libraries

Class Libraries

Class Libraries
Important Namespaces
The IdeaBlade.EntityModel.Entity
Finding Help on DevForce
XML Documentation
IntelliSense
The Object Browser
Class View
Class Diagram

This chapter takes you on a brief tour of the DevForce libraries.


The DevForce installer put a number of assembly DLLs in your installation directory and in the global assembly
cache (GAC, pronounced “gack”).

You do not have to put DevForce DLLs in the GAC on your application client machines. In most cases
your installation will likely be via XCOPY or ClickOnce.

You should turn to the Reference Help for the minute details about the DevForce classes, interfaces, methods,
properties and events. That effort can be like looking through a keyhole. Here you can learn a bit about the room
you‟re seeing through that keyhole.
While an assembly can host multiple namespaces and a DLL can host multiple assemblies, each DevForce
namespace is almost invariably in its own assembly and each assembly is in its own DLL. Thus, as our tour
proceeds namespace-by-namespace, we are also walking from one assembly and DLL to the next.
In this chapter we‟ll also give you some ideas about how to use Visual Studio to get to this information quickly.

Important Namespaces
All DevForce namespaces have an “IdeaBlade” prefix. The discussion below elides that prefix for brevity so, when
we refer to “EntityModel”, we mean “IdeaBlade. EntityModel”.
See the DevForce Reference Help or Consolidated Help for much more detail on the content of these namespaces.

96 | P a g e
IdeaBlade DevForce Class Libraries

DevForce Namespace Summary


EntityModel … Includes critical modeling and persistence
classes including Entity, EntityManager,
EntityQuery, EntityRelation, QueryCache,
QueryStrategy, and many others.
EntityModel.WS … Includes classes for modelling, querying, and
persisting Entities backed by web services.
Core … Includes classes to assist with databinding,
encryption, debugging, i/o, localization, tracing,
and other common development tasks.
Validation … Includes classes related to DevForce‟s
verification (validation) facilities.
DevTools.WinTraceViewer … Includes the TraceViewer, a utility that provides
a real-time display of DevForce tracing
messages; and the TraceViewerForm, a window
that provides an immediately display surface for
trace messages from a DevForce app.
Ado … Includes classes used for specific operations
performed directly against relational databases.
Can only be used from code executing on the
middle-tier server.

The IdeaBlade.EntityModel.Entity
The IdeaBlade.EntityModel.Entity is the base class of all of persistable business entities in your DevForce domain
model. Instances of Entity are not created directly.
Entity objects are managed and cached by a EntityManager. You'll use an EntityManager to create, retrieve and save
your entities. The EntityManager will also handle serialization and transfer of entities to a distributed Object Server.
When working with entity classes, basic properties for your business objects – those that map to columns of a
database, for example -- are auto-generated for you by the IdeaBlade DevForce Object Mapping Tool. You focus on
creating additional custom properties and methods to support business logic and rules.

The EntityAspect Property


Every instance of an entity has an EntityAspect property that is inherited from IdeaBlade.EntityModel.Entity.

C#
or aCustomer.EntityAspect
VB

Through the EntityAspect you have access to a rich set of (a) properties that define the entity‟s state, and (b)
persistence-related methods. These are listed below: method names terminate with parentheses.
See the DevForce reference help (IdeaBlade DevForce / Documentation / DevForce Help from the Windows Start
Menu) for more detail.

97 | P a g e
IdeaBlade DevForce Class Libraries

Table 1. EntityAspect Members

Member Function
AcceptChanges() Accepts all changes to this Entity, returning the EntityState to Unchanged.

AddToManager() Adds a newly created entity to its associated EntityManager.

ClearErrors() Clears any error messages on this Entity.

Delete() Marks this Entity for deletion; the EntityState becomes "Deleted".

Entity

EntityGroup The EntityGroup that this Entity belongs to. An EntityGroup is a container that
holds cached entities of a particular type.

EntityKey The EntityKey for this entity. Represents the primary key for an Entity.

EntityManager The EntityManager that manages this entity.

EntityMetadata The EntityMetadata for this Entity. The available EntityMetadata includes all of the
following properties:

Member Description

CanQueryByEntityKey Gets whether primary key queries are allowed.

ComplexTypeProperties Returns a collection of EntityProperties that


describe complex object properties for entities of
this type.

ConcurrencyProperties Returns a collection of EntityProperties that are


concurrency properties for entities of this type.

DataProperties Returns a collection of DataEntityProperties for


entities of this type.

DataSourceKeyName Gets the data source key name.

DefaultEntitySetName The default EntitySetName for entities of this type.

EntityProperties Returns a collection of EntityProperties that belong


to entities of this type.

EntityType Gets the Type of the entity.

IsComplexType Returns whether this metadata describes a


ComplexObject.

KeyProperties Returns a collection of EntityProperties that are


keys for entities of this type.

NavigationProperties Returns a collection of DataEntityProperties for


entities of this type.

98 | P a g e
IdeaBlade DevForce Class Libraries

Public Methods

Name Description

CreateEntity Creates a new entity of the type describe by this


metadata item.

GetDefaultValue

EntitySetName The name of the Entity Framework EntitySet containing this entity.

EntityState An enum signifying that the entity is Detached, Unchanged, Added, Deleted, or
Modified.

Equals() As defined on System.Object.

FindRelatedEntities() Finds any cached entities related to this entity by a specified EntityRelationLink.

ForcePropertyChanged() Forces a PropertyChanged event to be fired. This method should only be needed in
situations where changes to calculated fields or other properties not backed by an
EntityProperty must be made known.

GetDataProperty() Returns the EntityProperty corresponding to the specified property name.

GetHashCode() As defined on System.Object.

GetRelatedEntities() Returns all related entities via a specified EntityRelationLink. Differs from
FindRelatedEntities() in that it will retrieve the related entities from the back-end
data source of if necessary.

GetRelatedEntities<>() Generic version of GetRelatedEntities()

GetRelatedEntity() Returns a related entity via an EntityRelationLink.

GetRelatedEntity<>() Generic version of GetRelatedEntity()

GetType() As defined on System.Object.

HasChanges() Determines whether this entity has any pending changes. Returns a Boolean.

HasErrors Gets a boolean value indicating whether there are errors in this entity.
IsNullEntity Returns whether the current instance is a null entity. (The EntityManager will
return a NullEntity instead of a null value when a requested entity is not found.)

IsNullOrPendingEntity Returns whether the current instance is a null entity or a pending entity.

IsPendingEntity Returns whether the current instance is a pending entity. (The EntityManager will
return a PendingEntity instead of a null value when a requested entity is being
queried asynchronously and has not yet been returned.)

RejectChanges() Rejects (rolls back) all changes to this Entity since it was retrieved or had
Entity.AcceptChanges called on it.

RemoveFromManager() Removes the entity from the EntityManager cache.

99 | P a g e
IdeaBlade DevForce Class Libraries

SavingErrorMessage Gets the description of any error that occured during the most recent save of this
entity.

SetAdded() Forces this entity into the EntityState of Added. You will usually have no reason to
call this method from application code. The EntityState is automatically set to
Added by the framework when a new entity is added to an EntityManager.

SetModified() Forces this entity into the EntityState of Modified. You will usually have no reason
to call this method from application code. The EntityState is automatically set to
Modified by the framework when any EntityProperty of the entity is changed.

ToString() A string representation of this object.

VerifierEngine Gets the IdeaBlade.Validation.VerifierEngine shared by all entities within the same
EntityManager.

Finding Help on DevForce

Refer to the Reference Help (available from the IdeaBlade DevForce / Documentation menu option) for
information on DevForce classes.

For the detail help on individual types and their members, you can launch the Reference Help from the Start Menu
► All Programs ► IdeaBlade DevForce ►Reference Help.
There are some handy ways to get type and member level help within Visual Studio itself. These techniques are
great for exploring .NET and your own code as well as DevForce.
IntelliSense
Object Browser
Class View
Class Diagram

These are discussed in more detail below.

XML Documentation
These techniques are only as good as the XML documentation embedded in the code.

IntelliSense
You probably know that if you hover the mouse over a class or method for a moment, you‟ll see a tool tip that gives
some brief syntactical information.

IntelliSense can do much more. A full discussion is out of scope but we thought it worth mentioning a few of the
tricks we use all of the time.

100 | P a g e
IdeaBlade DevForce Class Libraries

Learn the key accelerators.


You‟ll find many of the IntelliSense accelerator keys listed in the Visual Studio Edit Menu.

We find List Members and Parameter Info to be the most informative short-cuts.12

 Press the accelerator key combination for List Members within an identifier and IntelliSense displays its
description straight from the <summary/> tag of its XML documentation. This works for a member, too.

 Enter the dot („.‟) to the right of an identifier and again you get an open list. IntelliSense scrolls to the most
recently used option if there is one. Clicking on the option reveals its description.

 Press the accelerator key combination for Parameter Info while your cursor is in the parameter list and
you‟ll see the method overloads and a description of the nearest parameter if the developer provided the
XML documentation
Use the up and down arrow to scroll among the overloads.

12
Word completion is a big help too although that‟s more about saving keystrokes than discovery. Type just a few letters of the
desired identifier, then signal work completion; VS fills in your choice.

101 | P a g e
IdeaBlade DevForce Class Libraries

The Object Browser


From the menu select View ► Object Browser (or press the accelerator key combination shown adjacent to that
menu option). Visual Studio adds an “Object Browser” tab showing all of the assemblies referenced by any project
in the solution.

There are three panes: a tree view of the assembly, list of member of the selected type, and the XML
documentation.

You can expand any node in the tree view to see its inheritance chain and examine any type within that chain:

Search
Best of all, you can search for a word (or part of a word) among your referenced assemblies and the .NET
framework assemblies.

102 | P a g e
IdeaBlade DevForce Class Libraries

In this example, we browse just among the solution assemblies and search for the word “manager”. After we click
the green search button, the result might look like this:

We can browse around in this filtered set as before. We click the “Clear Search” icon when we‟re done.

Class View
Class View is just like the Object View but narrows the scope to just the assemblies we can edit in our Solution. You
can open the Class View from the menu (View ► Class View).

Class Diagram
With both the Object Browser and Class View we can examine the assemblies but we cannot change their XML
documentation. We can only change the XML documentation of the code we‟re editing.
As with previous versions of Visual Studio we can enter the XML comments within the Code View.
Visual Studio 2005 introduced the Class Diagram which promises to be the means for programming visually. You
can open a class diagram in Visual Studio 2008 using the View Class Diagram button on the Class View toolbar.
The Class Diagram displays a lovely design service onto which we can drag our existing classes and mark
associations among them like so:

103 | P a g e
IdeaBlade DevForce Class Libraries

We can add classes and add members and the Class Diagram will stub in the code which we later flesh out by
switching back to Code View.
There are no round-tripping problems because the Class Diagram is just another view on the exact same code; there
is no intermediate format that must be translated as we shift from one perspective to the other.
This will be a wonderful documentation tool as soon as Microsoft smoothes some of the rough edges. You soon
discover that you‟re wrestling with positioning the blocks and the lines that connect them. Just as you have it about
right, you touch something and all of the graphics scramble to odd and inconvenient positions.
We are not enamored of the code generation either. It generates a nice stub but we can do better with code snippets
and, really, how hard is it to write a stub method or property? If you like it, by all means use it.

104 | P a g e
IdeaBlade DevForce Business Object Mapping

Business Object Mapping

Business Object Mapping


Introduction
Overview of the ADO.NET Entity Model
Working with the IdeaBlade DevForce Object Mapper
Object Mapper Walk-Through
Exiting The Object Mapper
The Object Mapper Menus
Injected Base Types
The Name Pluralizer: Fixing the Pluralization in Type Names
Mapping a Web Service
Notes on the Generated Code
Multiple Datasources
DataSourceKeys
Appendix: Many-to-Many Associations in the Entity Framework

The business object model consists primarily of “business objects”, the programmatic constructs that represent
entities in the domain of the application. In most cases, business objects represent entities in the “real world” such as
customers, employees, orders, and products.

Business Object Model development proceeds in the following steps:

 The developer uses the ADO.NET Entity Data Model Designer, or XML hand-coding, or a likely
combination of the two, to develop one or more Entity Data Models that describe conceptual entities and
map them to a back-end storage schema.
 The developer uses the DevForce Object Mapper to create a “Domain Model” that combines one or more
Entity Data Models into a single logical model. The resulting Domain Model encompasses entities from all
source Entity Models, uniting them in a single structure that permits relationships to be defined amongst
them and (at runtime) updates to be handled transactionally across them.
 The developer uses the Object Mapper to enhance and adjust the Entity Models in a variety of ways. The
changes made to the source Entity Model(s) by the Object Mapper do not conflict with the demands of the
Entity Data Model Designer, so the developer can work with confidence on her business model using either
or both tools, throughout the development life cycle.
 The Object Mapper then generates DevForce Entity class files from the Domain Model, just as the Entity
Data Model Designer generates class files from the Entity Model(s). These class files define the
specification for the runtime object model.
This chapter covers addresses the building of the Business Object Model. Another chapter, “Object Persistence”,
describes the use of that model in persistence operations such as queries, creates, updates, deletes and saves.

Introduction
The ADO.NET Entity Framework provides a mapping scheme for declaring how its persistence mechanisms should
translate between data in a data source and data in an in-memory business object state. The Entity Framework,
however, provides only for operation in client-server mode. Its object model includes detailed information about the

105 | P a g e
IdeaBlade DevForce Business Object Mapping

schema of the back-end data sources, and the Entity Framework depends upon locally available connection
information to carry out its persistence operations with those data sources.
The DevForce Composite Object Model, by contrast, is entirely free of knowledge or information about back-end
data sources, and so can be deployed to client machines without compromising the security of the persistent data
stores (which may be relational databases or web services). Furthermore, the DevForce persistence framework that
uses the Domain Model provides and manages a local cache in which data can be stored during application sessions,
permitting extensive and complex data maintenance work independent of the back-end data stores.
In short, DevForce extends the capabilities of the Entity Framework from client-server to n-tier; and in the process,
provides all the benefits of n-tier deployment. These include:

 client machines that are more secure;


 vastly better performance over low and moderate speed connections (such as typical internet connections);
 wider application reach (because of the reduced connection performance requirements);
 fuller use of client-side computing power and corresponding reduced server-side hardware demands;
 server statelessness and the concomitant ease of server scaling; and
 disconnected operation.
Because the DevForce Composite Object Model integrates multiple Entity Models, you business model can – unlike
in the Entity Framework – span multiple back-end databases. The DevForce model, again unlike the EF Model, also
supports business objects that are sourced from web services. Objects derived from all of these sources are
integrated into a single model, in which they are treated as equal members, have relationships, and participate in
single, integrated transactions.

Overview of the ADO.NET Entity Model


In the ADO.NET Entity Model you declare, for example, that anEmployee.FirstName, maps to the [FirstName]
column of [Employee] table records in a relational database.
The Entity Framework‟s persistence mechanism can take it from there. When it retrieves employee data from the
data source, it will know how to find the first name value and copy it into the EF version of the Employee business
object. When it saves the Employee business object, it will know how to extract the first name value and put it
where it belongs in the data source.
The DevForce persistence framework maps DevForce Entities to the corresponding Entity Framework objects. But
that is all happily behind the scenes for you. All you need to know is that DevForce uses the services of the Entity
Framework to perform the direct communications with back-end databases, and that, in order to do so, it knows how
to map its own entities to the EF ones. In your development world, you can happily ignore the latter; you will code
to, and in your application use, the DevForce entities exclusively.

Working with the IdeaBlade DevForce Object Mapper


The IdeaBlade DevForce Object Mapping (OM) tool, AKA “Object Mapper”, is a graphical designer for creating
and maintaining domain model from the starting point of one or more ADO.NET Entity Data Models. The Object
Mapper plugs directly into Visual Studio during DevForce installation.
Note that we do not address the creation or modification of the Entity Model using the Visual Studio Entity Data
Model designer or XML hand-coding. Those are topics for which Microsoft provides abundant documentation. If
you can put it in the Entity Model and make it work with the Entity Framework, we support it.

Object Mapper Walk-Through


This topic is arranged as a structured walk through the tool during which we create and elaborate a domain model
containing a collection of related business object entities based on tables in the NorthwindIB database.

106 | P a g e
IdeaBlade DevForce Business Object Mapping

1. Begin with an existing Visual Studio solution containing at least one project with an ADO.NET Entity Data
Model (EDM) in it.
The solution shown below has an EDM based on the NorthwindIB database that ships with DevForce. This
is a modified version of the NorthwindEF database distributed by Microsoft. 13

2. Launch the Object Mapper from the Visual Studio menu:

The Object Mapper launches with no Entity Data Model loaded in this first-time use:

13
A discussion of the differences between NorthwindIB and NorthwindEF, and the reasons for them, is included as an Appendix
to this chapter.

107 | P a g e
IdeaBlade DevForce Business Object Mapping

3. Click on the Domain Model name – “(New Model)” -- in the tree control to select it. Now inspect the
properties of the Domain Model in the Detail pane.

108 | P a g e
IdeaBlade DevForce Business Object Mapping

Domain Model Project. This will display the path to the folder for the Domain Model project and files.
Use the <New project…> button to create a new Domain Model project; otherwise select an existing
project from the dropdown list.

Create Silverlight Domain Model Project checkbox. Check this box if you wish to create a Silverlight
project. (See the chapter, “DevForce Silverlight Apps” for more detail.) This option is only available in the
DevForce Silverlight and DevForce Universal products; not in DevForce WinClient.

Namespace. Code for the Domain Model will be generated into the namespace shown in the Settings area.
By default, the namespace will be “DomainModel”. You can change this if you prefer a different name for
the namespace.

Entity Manager Name. By default, the container for the Domain Model‟s collections of objects will be
“DomainModelEntityManager”. You can change this as well. You will be able to reference its collections
in LINQ and elsewhere as follows:

C# DomainModelEntityManager _em1 = DomainModel.DomainModelEntityManager.DefaultManager;

VB Dim _em1 As DomainModelEntityManager = DomainModel.DomainModelEntityManager.DefaultManager

Generate Code after save. This option determines whether the the Object Mapper will generate code
when you save your Domain Model. You can selectively turn on or off the generation of two types of code:

 Create Developer Partial Class Files


If this option is checked, the Object Mapper will generate a developer partial class in a stand-alone
file for each entity in the model for which it does not find such a file already in existence. It will
never overwrite an existing partial class developer file.

 Update model project‟s app.config


Determines if the app.config file (written in XML) will be generated or updated.

.NET Language. This option determines the .NET language in which the class files will be generated. You
can choose C# or VB.

Copy Entity Model Artifacts. This option determines whether the Object Mapper will generate statements
in the DomainModel project‟s post-build event handler to move necessary DLLs and Entity Data Model
components to the executables directory for the solution. Without these statements you will have to move
the files yourself whenever you change and rebuild the model.
File Name. This setting displays the file name (path) to the folder where the Domain Model (.ibedmx) file
is (or will be) saved.

4. Add an Entity Model to your Domain Model.

109 | P a g e
IdeaBlade DevForce Business Object Mapping

Select Model / Add Entity Model to add


an existing Entity Model to your Domain
Model.

You will be presented with an Open File


dialog. If there is an Entity Data Model
already in your Visual Studio solution,
the Object Mapper will find it. Otherwise,
or if your solution has multiple Entity
Data Models and you wish to select a
different one than the Object Mapper
selected, browse to the desired Entity
Model file.
Click <Open> when the desired EDM is
selected in the dialog to add it to your
domain model.

110 | P a g e
IdeaBlade DevForce Business Object Mapping

5. Inspect the Properties of the Entity Model:

File Name. This setting displays the file name (path) to the Entity Model (.edmx) file. This is the location
to which you browsed when adding the Entity Model to the Domain Model.

Data Source Key Name. The data source key name is the symbolic name that will be used by DevForce
for this Entity Model, and by extension, the database to which it is bound.

Namespace. Code for the Entity Model will be generated into the namespace shown. The namespace
shown is the one currently encoded into the Entity Model (.edmx) file. If you have not previously changed
it in the Object Mapper, it is the namespace you selected when creating the Entity Model, or which you
later changed in the Entity Model.

You can change this name if you prefer.

Container Name. The container for the entity model‟s collections of objects is a
System.Data.Object.ObjectContext whose name is encoded into the Entity Model (.edmx) file. Note that
you are unlikely to use this container directly in your DevForce app, since you will be working through the
Domain Model‟s DomainModelEntityManager container and its collections.

C# DomainModelEntityManager _em1 = DomainModel.Manager.DefaultManager;

VB

111 | P a g e
IdeaBlade DevForce Business Object Mapping

Injected Base Types. This button launches a dialog to help you define base classes to sit in the inheritance
hierarchy of your business type classes. This will be discussed below.

Name Pluralizer. This button launches a tool to help you save a great deal of time making the pluralization
of your type names orderly and sensible. This will be discussed below.

Verification Interceptors. These options give you control over which verification-related interceptors are
coded into your generated property definitions. You can choose among the options shown below:

Verification interceptors are discussed in Chapter 7, “Validation Through Verification,” of this Developer
Guide.

6. Click on the ServerModelNorthwindIbContext node in the navigational tree control. When you‟re
positioned in the tree on a container node such as this one, you have a comprehensive, quick reference view
of the entity types and their properties:

112 | P a g e
IdeaBlade DevForce Business Object Mapping

For each Entity type you see:

 a read-only IsModified property indicating whether the type definition has been modified in the current
Object Mapper session
 the name of the Entity Set that will provide instances of the type
 whether the type is abstract
 the type‟s Base type, if it inherits from another type.

7. Click in the tree control on one of the individual types. You‟ll see only the properties and associations for
that type:

8. Select the Navigation Properties tab in the detail pane.

This shows you the properties defined in the ServerModelNorthwindIB Entity Model for navigating from
the Order object to its related entities. The Order has a Customer who placed it, an Employee who wrote it,
a collection of line items (which are OrderDetail objects), and, potentially, a related InternationalOrder.
These properties were generated by the EDM Designer in response to its discovery of relationships in the
targeted database schema.

113 | P a g e
IdeaBlade DevForce Business Object Mapping

9. Select the Associations tab. You will see the background information about the associations defined in the
model (which led to the generation of the navigation properties you just examined).

A foreign key constraint, FK_Order_Customer, found in the database, relates the Order and Customer
tables in a many-to-1 relationship. (One customer can place many Orders.)
Another, FK_Order_Employee, relates the Order and Employee tables in a many-to-1 relationship. (One
Employee can write many Orders.)
A third, FK_OrderDetail_Order, relates the Order and OrderDetail tables in a 1-to-many. (One Order can
have many details – line items).

114 | P a g e
IdeaBlade DevForce Business Object Mapping

10. Return to the Simple Properties tab and examine the detail on the properties of the Order Entity:

On this grid, you can:


 Rename a property;
 Add it to, or remove it from, the type‟s primary key;
 Observe its datatype ;
 Make it nullable or non-nullable;
 Designate it as a column to be considered when checking for concurrency conflicts, and ask DevForce
to automatically update it for you as needed, according to a variety of strategies;
 Change Getter and Setter Access levels

Continuing to the right on the Simple Properties grid, you can:

 Set the property‟s Getter and Setter access types;


 Turn generation of Validation Attributes on or off;
 Set the Verification Setter Mode;
 Observe the property‟s maximum allowed length, for data types for which that property is defined;
 Observe whether the property‟s length is fixed;
 Observe the property‟s Precision and Scale.

115 | P a g e
IdeaBlade DevForce Business Object Mapping

Detail on the Property Settings

Many of the property settings are self-explanatory, but a few need further explanation:

Concurrency Strategy

Six strategies are defined, as shown here:

These are discussed in detail in Chapter 6, “Object Persistence”, in the “Basic Mechanics of Concurrency Detection”
section.

Getter Access
The Getter Access setting determines with what access type the property getter will be generated. The options are:
Public, Protected, Internal, Private, and Protected Internal.

Setter Access
Setter Access options are similar to those for the Getters, with the additional option of None (which causes the setter
not to be generated at all).

Generate Validation Attributes


This option controls whether Validation attributes are included in generated property definitions.

Verification Setter Mode

116 | P a g e
IdeaBlade DevForce Business Object Mapping

The Verification Setter Mode controls what verification-specific interception points will occur for the generated
properties. Verification interceptors are in Chapter 7, “Validation Through Verification”.

Exiting The Object Mapper


If you attempt exit the Object Mapper with unsaved changes, you‟ll see the following dialog:

Clicking <Yes> will save the domain model with a default name and location, those being:

 Name. The domain model will be given the same name as the (first) Entity Data Model added; and
 Location: The domain model will be saved into the same project as the Entity Data Model most recently
added.
If you wish to exert explicit control over where the domain model is saved and what it is named, click <Cancel>
when you see the above dialog, then select the domain model node in the navigation pane of the Object Mapper
dialog (which will have the title “(New Model)” if the domain model has not previously been saved):

Using the ComboBox labeled Domain Model Project, you can specify into which of your solution‟s projects the
domain model will be generated, or you can click the <New project…> button to create a new project just for the
domain model.

117 | P a g e
IdeaBlade DevForce Business Object Mapping

By default, a new project will be named “DomainModel”. Note that the domain model itself, encoded in a file with
extension .ibedmx, will always be named the same as the folder into which it is generated.
When you create the new project, a solution folder of the same name will be created, and both the domain model
project and all existing Entity Data Model projects that are part of the domain model will be moved into that
solution folder. The domain model itself will be generated and saved only when you so order.

The Object Mapper Menus


Menu Options

The File menu contains options related to the Domain Model file as a whole.

The Model menu contains options related to operations on the Entity Model components of the domain model.
Many of these options are also available on right-click shortcut menus associated with nodes of the navigation tree.

118 | P a g e
IdeaBlade DevForce Business Object Mapping

The View menu contains a toggle option to suppress or display the navigation window (tree control).

The Help menu contains an About option from which you can learn the version number and license type associated
with the copy of DevForce you have installed.

Injected Base Types


In the Detail pane for an Entity Model we previously noted the presence of a button to launch something called
“Injected Base Types...”.

Clicking this button launches the following dialog:

119 | P a g e
IdeaBlade DevForce Business Object Mapping

This dialog permits you to introduce base types between levels in your inheritance hierarchy. The most common
operation will be to create a base entity that inherits from IdeaBlade.EntityModel.Entity, and to make it the base
type for all business object types in the model. This you would do as follows:
1. Click the <Add> button.
2. In the “Add Injected Base Type” window, type the desired name of your base entity, and accept the
inheritance default of “Entity”.

3. Click OK. You‟ll see the following dialog:

4. Now select the row created for BaseEntity and click <Set Default>.

120 | P a g e
IdeaBlade DevForce Business Object Mapping

BaseEntity becomes the default base class for all your business object types.

You can all other base types and assign any subset of your business object types to inherit from them. Note,
however, that you can insert at most one base type between each pair of concrete types. Thus, you can have Order
inherit from BaseEntity and International Order inherit from NonDomesticOrder; but you can‟t use the Injected
Types dialog to make Order inherit from OrderBase and OrderBase inherit from BaseEntity. If you need to do that,
do the following:
1. Define base types BaseEntity and OrderBase.
2. Make BaseEntity the default type.
3. After close the Injected Types dialog, set Order to inherit from OrderBase.

4. Save and generate the model.


5. Manually change the code for the OrderBase class to make OrderBase inherit from BaseEntity. (Note that
you will have to re-do this last step if you regenerate OrderBase.)
We stopped short of providing facilities to define such multi-level inheritance stacks in the Injected Types dialog in
order to keep the UI simple.

The Name Pluralizer: Fixing the Pluralization in Type Names


In the Detail pane for an Entity Model we previously noted the presence of a button to launch something called the
“Name Pluralizer”.

121 | P a g e
IdeaBlade DevForce Business Object Mapping

The Name Pluralizer can also be launched from the main menu via Edit / Name Pluralizer, or from the context menu
associated with the Entity Model node in the navigational tree control:

Launching the Name Pluralizer by any of these mechanisms presents you with the following modal dialog:

We‟ll get to the mechanics of this tool in a moment, but let‟s get a little background first.
The Entity Model Designer, by default, always names the Entity Sets and Entity the same as the table on which they
are based. In the NorthwindIB database, tables were named in the singular (“Customer” rather than “Customers”,

122 | P a g e
IdeaBlade DevForce Business Object Mapping

etc.), so both the Entity Types and the Entity Sets are named in the singular as well. We‟re going to want the Entity
Set names to be plural, as we find it more natural to refer to the “Customers” collection, which contains individual
“Customer” entities.
The EDM Designer also makes no attempt to address the number (singular or plural) of the navigation properties. It
simply names them the same thing as the corresponding Entity. You may want to do better than that. Most
developers want the navigation properties that return a collection to have plural names (“OrderDetails” instead of
“OrderDetail”), and the navigation properties that return a single object to have singular names (“Customer”,
“Employee”, “InternationalOrder”).
Now let‟s take a second look at the Name Pluralizer dialog, first shown above. The Name Pluralizer will fix our
pluralization problems with both Entity Set names and Navigation Property names at one stroke. You can change
them in either direction, but for most developers, the default settings will be perfect. Regardless of what their names
are now, Entity Sets and Navigation Properties that return a collection will end up with plural names; Entity Types
and Navigation Properties that return a single object with singular ones. (The <Reset Defaults> button will always
re-establish the settings you see here.)

You just click <OK> or <Apply> to perform the work. (The <Ok> and <Apply> buttons have the standard
Windows behaviors: both perform the indicated operations, but <Ok> will also close the dialog, whereas <Apply>
will leave it open.)

123 | P a g e
IdeaBlade DevForce Business Object Mapping

Note the pluralization of the Entity Set and Navigation Property names after applying the Pluralizer. This will save
you a lot of time, particularly if your model is large!

Mapping a Web Service


The process of adding a web service to your domain model will result in a service reference being added to
the project that is selected at the time you add the web service. Since such a service reference should not be
included in a project destined for client-side deployment, you should, before launching the Object Mapper
to add a web service to your domain model, select such a project. If you don‟t already have a suitable
project targetted for server-side-only deployment, create one.

To add web service entities to your domain model, begin by selecting File / Add Web Service from the Object
Mapper menu:

124 | P a g e
IdeaBlade DevForce Business Object Mapping

In the resultant dialog, enter the URL of the desired web service:

125 | P a g e
IdeaBlade DevForce Business Object Mapping

The web service returns information about what Services and Operations it provides:

A .NET class will be generated to facilitate your access of this web service. Change the namespace that will be used
for the code in this class if desired:

This will result in an additional EntityModel node in the navigation pane, and a new container (ObjectContext) with
classes corresponding to the output of the web service. Such classes will become types in your DomainModel, peers
of those generated from database tables.

126 | P a g e
IdeaBlade DevForce Business Object Mapping

Notes on the Generated Code


The DevForce Object Mapper generates a great deal of code into its designer class file. Let‟s have a look at the code
for generated properties.

Data Properties
First, let‟s consider the generated code for a simple property. We‟ll look at the CompanyName property of the
Customer object in the NorthwindIB database. Here is the complete generated code for this property:

C# #region CompanyName
/// <summary>Gets or sets the CompanyName. </summary>
[Bindable(true, BindingDirection.TwoWay)]
[Editable(true)]
[Display(Name="Company Name", AutoGenerateField=true)]
[IbVal.ValidateProperty]
[IbVal.StringLengthVerifier(MaxValue=40, IsRequired=true)]
[IbCore.MaxTextLength(40)]
[MsSer.DataMember]
public String CompanyName {
get { return CompanyNameEntityProperty.GetValue(this); }
[System.Diagnostics.DebuggerNonUserCode]
set { CompanyNameEntityProperty.SetValue(this, value); }
}
#endregion CompanyName property

VB #Region "CompanyName property"

'''<summary>Gets or sets the CompanyName. </summary>

127 | P a g e
IdeaBlade DevForce Business Object Mapping

<Bindable(true, BindingDirection.TwoWay)> _
<Editable(true)> _
<Display(Name:="Company Name", AutoGenerateField:=true)> _
<IbVal.ValidateProperty> _
<IbVal.StringLengthVerifier(MaxValue:=40, IsRequired:=true)> _
<IbCore.MaxTextLength(40)> _
<MsSer.DataMember> _
Public Property CompanyName() As String
Get
Return CompanyNameEntityProperty.GetValue(Me)
End Get
<System.Diagnostics.DebuggerNonUserCode> _
Set
CompanyNameEntityProperty.SetValue(Me, value)
End Set
End Property
#End Region

See the chapter “Property Interceptors” for a detailed discusion of how you can write code to intercept Gets and Sets
to change the delivered or received value, perform security checks, or perform any desired related operation.
There is even more information in the CompanyNameEntityProperty that we have so far described. As it so happens,
EntityProperty has two subclasses, DataEntityProperty and NavigationEntityProperty, which contain additional
information. Since CompanyName isn‟t a navigation property, but rather a simple data property,
CompanyNameEntityProperty is generated into the designer code as a DataEntityProperty. That has the following
members:

As you can see, the information you have available about the CompanyName property to your interceptor methods
is quite rich indeed. In addition to the things we‟ve seen before, you have the property‟s default value, and you can
tell if its value is autoincremented, if it is a complex type, if it is designated as a property to be checked for the
determination of data concurrency, and if it belongs to its containing object‟s key.

Navigation Properties
Now let‟s look at the definition for a navigation property. These, you may recall, are generated when relations are
defined between types. The Customer type, for example, is involved in a one-to-many relation with the Order type: a
given Customer can place many Orders. So the DevForce Object Mapper generated an Orders property into the
Customer class:

C# #region Orders property

/// <summary>Gets the Orders. </summary>


[Bindable(false)]

128 | P a g e
IdeaBlade DevForce Business Object Mapping

[Display(AutoGenerateField=false)]
[MsSer.DataMember]
[IbEm.RelationProperty("FK_Order_Customer", IbEm.QueryDirection.ToRole2)]
public IbEm.RelatedEntityList<Order> Orders {
get { return OrdersEntityProperty.GetValue(this); }
}
#endregion Orders property

VB #Region "Orders property"

'''<summary>Gets the Orders. </summary>


<Bindable(false)> _
<Display(AutoGenerateField:=false)> _
<MsSer.DataMember> _
<IbEm.RelationProperty("FK_Order_Customer", IbEm.QueryDirection.ToRole2)> _
Public ReadOnly Property Orders() As IbEm.RelatedEntityList(Of Order)
Get
Return OrdersEntityProperty.GetValue(Me)
End Get
End Property
#End Region

The code for the Orders property has many similarities to the CompanyName property we previously examined, but
some important differences as well. Whereas CompanyName returned a simple string type, Orders returns a
RelatedEntityList<Order>. It has an attribute that flags it as a RelationProperty (the term is synonymous with
“navigation property”), and which specifies the relation type (FK_Order_Customer) that connects Order to
Customer and indicates that Order is the child in that relation.
Note that a ChildrenReference<Order> property is also defined in the generated code. This property, named
Orders_Reference, allows you, among other things, to examine the navigation property before the fact to determine
if it returns a scalar value or a list. The name “Orders” sure looks like something that returns a collection, but that
just a happy consequence of the way the modeller named it, and not an easy or reliable way to make the
determination!
Since Orders is a navigation property, its corresponding EntityProperty is generated as a NavigationEntityProperty.
The following information is available on such a property, above and beyond the information we say previously that
belongs to all EntityProperties:

Note that you can tell which side of a relation the type returned by the property is on (QueryDirection), and which
relation is involved (RelationName).

Options for Getting and Setting Property Values


Note that you have two distinct ways of requesting or setting the value of a given property, shown below. All of the
syntaxes shown result in operations that are routed through the property‟s getter or setter logic14:

14
See the “Property Interceptors” chapter to learn how to bypass the getter and setter logic for those cases where you
need to.

129 | P a g e
IdeaBlade DevForce Business Object Mapping

Get
 anEmployee.LastName
 Employee.LastNameEntityProperty.GetValue(anEmployee)

Set
 anEmployee.LastName = “Jones”
 Employee.LastNameEntityProperty.SetValue(anEmployee, “Jones”)

Another overload of GetValue() permits you to access the several different versions of value of the property:

Current is the value you retrieve using the two Get syntaxes first shown. Original is the value as last retrieved from
the data source, before any local changes (if any) were made. Just before a change is made to the Current value of
an entity, it has a Proposed value which may or may not be allowed depending upon setter logic. The Default value
depends upon the entity‟s EntityState. For example, the Default value is the Current value for an entity in any of the
following EntityStates: Added, Modified, or Deleted. For an entity in the states EntityState.Detached or
IEditableObject.Edit, the Default value is the Proposed value.

EntityManager.GetEntityGroup
In the cache, entities of a single type are stored in a container called an EntityGroup. You probably won‟t find much
direct need of this container, but it does raise a few low-level events that can be useful in very specific situations,
those being:
 EntityPropertyChanging
 EntityPropertyChanged
 EntityChanging
 EntityChanged
The first two fire when a property is changed, and are specific to that property; the last two fire when anything about
an entity changes (including a property). If you‟re alert, you may note that those occurrences seem pretty well
covered by the corresponding interceptors in the property setters that we just finished discussing:

Event on EntityGroup Corresponding Interceptor methods generated into the Entity class
EntityPropertyChanging Before{PropertyName}Set
EntityPropertyChanged After{PropertyName}Set

EntityChanging BeforeSet
EntityChanged AfterSet

And you‟d be right: for most things you need to be in response to a change in an entity, the interceptor methods are
the vehicle of choice. But the EntityGroup events offer one advantage over the latter: because they are implemented

130 | P a g e
IdeaBlade DevForce Business Object Mapping

as event handlers, they can be added and removed dynamically at runtime. If you need to do something conditioned
upon runtime circumstances, they‟ll be just the ticket.
You can get an instance of the EntityGroup for a type (we‟ll use Customer again) from an EntityManager as follows:

C#
EntityGroup customerGroup = anEntityManager.GetEntityGroup(typeof(Customer)) ;

VB

You can also get the EntityGroup associated with a particular entity:

C#
aCustomer.EntityAspect.EntityGroup

VB

Multiple Datasources
Some business object models unite data from multiple data sources. Order information, for example, might reside in
both an application-specific database and in an accounting database. We might need to read from both. We might
need to post to both. We can accommodate these requirements within a single business object model 15.
We began the mapping session illustration by supplying a database connection string. We then examined the
database objects, picked a few, mapped them, and generated their classes.

15
The data sources can be a mix of databases and web services. We illustrate this discussion with database data sources.

131 | P a g e
IdeaBlade DevForce Business Object Mapping

DataSourceKeys
We neglected to mention that the concrete business objects we declared were attached permanently to a
particular DataSourceKey. We associated that key with a database accessible via the connection string. Let’s revisit
the moment after setting that connection string.

Notice the DataSourceKey property associated with the Entity Data Model. In the screen shot, above, the property
has the name “Default”, which is the default value given to this property by the Object Mapper. But this can be
renamed as desired by the developer.
In the Entity Data Model, the DataSourceKey name is stored as a DataSourceKey attribute of the Schema element
within the ConceptualModel. This tells you that there is a one-to-one mapping between a DataSourceKey and a
particular Entity Data Model (EDM). Since a single DevForce DomainModel can encompass multiple EDMs, there
is then a one-to-many relationship between a DomainModel and its DataSourceKeys.
In the app.config file, the name of a DataSourceKey is stored in a name attribute of an edmKey element. In that file,
the Object Mapper generates one edmKey for each Entity Data Model included in the DomainModel. Each edmKey,
besides having a name, also has a connection attribute whose value includes both the location of the entity model
artifacts (.csdl, .ssdl, and .msl files) and the connection string for the database. When the Object Mapper generates
its version of the app.config initially, this connection string is brought over intact from the app.config file associated
with the underlying Entity Data Model.

132 | P a g e
IdeaBlade DevForce Business Object Mapping

You might assume, at first blush, that there is necessarily exactly one edmKey for each DataSourceKey. But that
isn‟t the case. While there can be, at runtime -- at a given moment -- only one physical data source associated with a
given Entity Data Model, DevForce provides you with the flexibility to assign different physical datasources to a
given EDM at different times. For example, you might have Development, Test, and Production versions of a given
database. All have the same schema, but contain different data. You can decide for a given runtime session of the
application which database should be used to supply data to Entity Data Model XYZ. You can even switch the
datasource out dynamically while running!
To use this capability, you add additional edmKey elements manually to the app. config, so that it will end ups with
multiple edmKeys for at least some of the Entity Data Models that comprise your Domain Model. Because of this
capability, the actual formal relationship between Entity Data Models and edmKeys is one-to-many.
On the other hand, there should be exactly one edmKey for each physical database that can contribute data to a
given DomainModel at runtime. Thus the relationship of edmKeys to physical databases is 1-to1.
The relationship between these various elements is summarized in the following sidebar and diagram:

Models, Keys, and Data Sources


The diagram at right summarizes the
relationships between models, keys, and data
sources.
One DomainModel may be associated with
many Entity Data Models (EDMs).
A given EDM is associated with a single
DataSourceKey.
A given EDM / DataSourceKey may be
associated with many Entity Types.
A given EDM / DataSourceKey may be
associated with many edmKeys (in the
app.config file).
Each edmKey represents a single physical data
source.

The DataSourceKey represents a schema: for a given EDM, the DataSourceKey identifies the Datasource schema to
which the EDM‟s conceptual model is mapped. Every business object has a DataSourceKeyName property, defined
in the (Entity) class that was generated by the DevForce Object Mapper to contain the object‟s blueprint.Any data
source used at runtime to supply data for that business object must have the same schema as the data source to which
that business object type is mapped in the Entity Data Model.

Appendix: Many-to-Many Associations in the Entity Framework


In this appendix we examine alternative ways of modeling many-to-many associations in Entity Data Models.
Specifically, we‟ll look at three different models, all based on the NorthwindIB database 16, that link Employees and
Territories in a many-to-many relationship. The central differences between the three relate to how the linking entity
in the many-to-many association is modeled. Accordingly, we describe them with reference to that entity:
 Exposed Linking Entity With Payload

16
NorthwindIB is our IdeaBlade version of the NorthwindEF sample database distributed by Microsoft. We‟ve made a variety of
changes to permit us to illustrate different capabilities of DevForce and the Entity Framework, but the two database remain
substantially similar.

133 | P a g e
IdeaBlade DevForce Business Object Mapping

 Non-Exposed Linking Entity With No Payload


 Exposed Linking Entity With No Payload

We‟ll see how each of the three situations is modeled in the Entity Framework, and discuss some pros and cons.

Introduction
“Payload” is the term used by the Entity Framework designers to describe columns in a linking entity other than the
foreign keys to the two external items that the linking entity connects. In the database diagram below,
EmployeeTerritory is the many-to-many linking table between the Employee and Territory tables. It has two foreign
keys, EmployeeID and Territory ID, which link it to those tables (in many-to-1 relationships). But it has also has ID
and RowVersion columns which, whatever their business function, constitute payload in the linking entity.
Figure 2. Linking Table with Payload

The following database diagram, on the other hand, depicts a linking table, EmployeeTerritoryNoPayload, which
has only the two foreign key columns.
Figure 3. Linking Table with no Payload

134 | P a g e
IdeaBlade DevForce Business Object Mapping

As it so happens, the Entity Data Model (EDM) Wizard in Visual Studio, which is launched when you add an
ADO.NET Entity Data Model item to a project, generates very different conceptual models from these two sets of
table schemas. Let‟s have a look.

Exposed Linking Entity With Payload


If you run the EDM wizard against the set of tables in Figure 1 and do a bit of renaming on the Navigation
Properties to make their function clearer, you get an EDM that looks like the following:

135 | P a g e
IdeaBlade DevForce Business Object Mapping

Notice the many-to-1 associations17 between EmployeeTerritory and Employee, and between EmployeeTerritory
and Territory. Taken together, they define a many-to-many association between Employee and Territory, but that
association is not explicit.
Notice also that the EDM wizard created a navigation property in the Employee entity to return the employee‟s
collection of associated EmployeeTerritory objects. It named that property EmployeeTerritory; we renamed it to
“EmployeeTerritories” to make clearer that it returns a collection.
The wizard created a corresponding navigation property in the Territory entity, which we also renamed.
If we want the collection of Territory entities that are associated with a particular Employee entity, we‟ll either have
to iterate through its collection of EmployeeTerritory entities, grabbing the Territory associated with each and
setting it aside in a list; or write a query to retrieve them. Whatever operation we choose to use to compile the list we
can of course embed in a property or method of our Employee class to give us convenient access to the desired
associates.

Non-Exposed Linking Entity With No Payload


If you run the EDM wizard against the set of tables in Figure 2 and again do a bit of renaming on the Navigation
Properties, you get an EDM that looks like the following:

Good heavens: what happened to the linking entity, EmployeeTerritory?

17
If you‟re new to the Entity Framework and/or to object modeling, just be aware that what are called “relationships” between
tables in a database are referred to as “associations” between the corresponding objects in an Entity Framework conceptual
model. For practical purposes the terms “relationship”, “relation”, and “association” frequently get used more or less
interchangeably in discussions of these object models, even though, technically, in UML terminology, an association is just
one subtype of relationship.

In this paper I‟ve used the term “relationship” when speaking of tables in a database, and “association” when speaking of entities
in an object model. I attempt no finer distinction than that.

136 | P a g e
IdeaBlade DevForce Business Object Mapping

As it turns out, the EDM code generator decided that its function was entirely to associate the Employee and
Territory entities, and that it therefore needed no explicit presence in the model. Instead, it chose simply to describe
the many-to-many association between Employee and Territory.
It also created a navigation property in the Employee entity to return the collection of associated Territories. Since it
doesn‟t attempt meaningful pluralization, it named this property “Territory”; we renamed it to “Territories”.
Corresponding, it created a navigation property in the Territory entity to return the related Employees. This, of
course, it named “Employee”; we renamed it to “Employees”.

The Issues
While the invisible linking entity used for the payload-free linking table has its attractive aspects, it also means that
you must necessarily live with two different ways of modeling many-to-manys in your Entity Data Model. You
can‟t always live without a payload in your linking entity. Consider, example, an Order entity that links Sales
Representatives to Customers in a many-to-many association. The Order is important in its own right, and is likely
to carry a great deal of important informational baggage. It certainly must be exposed in your business model,
whatever its function as a linking entity.
The other issue is that “pure”, payload-free linking entities sometimes, over their lifetimes, need to grow a payload.
You may find that you wish to record certain pieces of information about the association itself. When was said
Territory assigned to said Employee? Who made the assignment? Why was the assignment made?
The moment you add payload, you will have to change your model, because the linking entity can, by the rules of
the ADO.NET Entity Data Model, no longer remain unexposed. Furthermore, the explicit many-to-many association
it formerly defined is no longer supported. So you will have to rewire that many-to-many association as a pair of
many-to-1s. This isn‟t terribly hard if you know what you‟re doing with the EDM, but we would certainly advise
you to practice the job offline, in a small test model, before you try it on your real EDM. And make a backup of the
latter before you start hacking away at it. It‟s all too easy to hose it up, at which point you will have no choice but to
don your swamp boots and head bravely into a mosquito-infested swamp of XML.

Advantages of Standardizing on Linking Entities Having Payload


It often happens, as a business model evolves, that linking entities which began life as pure utililitarian connectors
come to need additional properties to describe their state satisfactorily, and therefore to need a payload. If that‟s
what the business model demands, one doesn‟t want to create a disincentive for adding such a payload when the day
comes that it is discovered to be needed. Knowing that adding even a single extra column to a linking entity will
force us to make a non-trivial18 change to our model might make us think twice about adding a column we really
need. To head this off at the pass, we might make the design decision that our linking entities should always be
exposed in the model, from day one. Then adding a column to a table, and a corresponding property to an entity in
our conceptual model, will be a very simple operation.
We might also prefer that every one of our tables and every one of our entities have an arbitrary, single-column
primary key. When all of our entities, linking or otherwise, have the same kind of key, we work with all of them in
exactly the same way, and we don‟t have to explain why a certain entity (which formerly didn‟t have, but now does
have, plenty of payload columns) has a two-property primary key when the rest have single-property primary keys.
“Is there a reason for that?” a new developer on our team asks. “Sure,” we answer. “It‟s historical.”
No thanks!
Applying the inclination for single-column primary keys to linking entities means you automatically also get linking
entities that have payload and are explicitly exposed in your model. Thus, you gain two aspects of standardization
for the price of one: (1) consistent primary key styles and (2) consistent modeling of linking entities and many-to-
many associations across your model and down through time.

18
all in the eye of the beholder, of course

137 | P a g e
IdeaBlade DevForce Business Object Mapping

Wait a Minute: What About the Navigation Properties?


If you‟re very alert you may have noted that pursuing the above-discussed key and linking-entity-exposure
preferences does leave us without one thing we get from the EDM‟s default treatment of payload-free linking
entities: the many-to-many navigation properties. Isn‟t that a big disadvantage? Now we now have to write them
ourselves?
It‟s true, we do, so let‟s see how hard it is.
In a DevForce application, we end up writing all our code against the Entity classes generated by DevForce rather
than those generated by the Entity Framework (which, strictly speaking, are
System.Data.Objects.DataClasses.EntityObjects rather than IdeaBlade.EntityModel.Entities). So if I‟m going to
write a Territories property to return the Territories associated many-to-many with an Employee, I‟ll do it in the
Employee partial class generated by DevForce in a standalone Employee.cs (or .vb) file. Here‟s what the property
looks like:

C# public List<Territory> Territories {


get {
var query = this.EntityManager.EmployeeTerritories
.Where(et => et.Employee.EmployeeID == this.EmployeeID)
.Select(et => et.Territory)
.Distinct();
return query.ToList();
}
}

VB

The next 30 of these look pretty much like this one: I just substitute the appropriate entity types and key properties.

Exposed Linking Entity With No Payload


For our final exercise, let‟s suppose, hypothetically, that you‟re one of those wrong-headed people who disagree
with me and happen to like, for linking entities, multi-column primary keys consisting of the two foreign keys. But
let‟s also propose that you are persuaded by the utility of modeling a payload-free linking entity in a manner that
allows it to acquire payload later with a minimum of upheaval in your model.
Is there some way you can get the payload-free linking entity to show up, and with the same many-to-1 associations
coming out of it that it will have to have later when it does have a payload?
Yeah, okay, if the answer were “no”, I probably wouldn‟t have brought it up. Here‟s a picture of such a model:

138 | P a g e
IdeaBlade DevForce Business Object Mapping

The unfortunate thing is that this is not an option provided by the EDM designer. You‟ll have to do a bit of
twiddling. The dirty details would make this article too long and take it off course, but here‟s a prescription by
which you can figure them out yourself.
You can do everything below, except the final step, in the EDM designer:
1. Create a linking entity that has a payload (anything!) and build a model that uses it. The best model for this
purpose is probably one that contains exactly three entities, like the ones we‟ve looked at in this article.
2. Remove the payload columns from the linking entity‟s backing database table.
3. Update the EDM using its “Update Model from Database” option.
4. Delete the properties corresponding to the payload column or columns that you removed.
5. Add the two foreign key properties to the conceptual in the EDM as explicit scalar properties.
6. Designate the two foreign key properties as primary keys.
7. Flesh out the table mappings for the two newly added properties, and for the two many-to-1 associations.
8. Examine the Before and After model to see what‟s different.

If you‟re free to change the database on which the model is built, you might be able to use the above technique
directly on your actual development model. Otherwise you‟ll have to spend enough energy on the last step above to
be able to reproduce the result in your model. It‟s not rocket science, but it‟s not quite falling off a log, either.

139 | P a g e
IdeaBlade DevForce Property Interceptors

Property Interceptors

Property Interceptors
Named vs. Unnamed Interceptor Actions
Interceptor Chaining and Ordering

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.

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)

C# public partial class Employee : IdeaBlade.EntityModel.Entity {


public String LastName {
get { return LastNameEntityProperty.GetValue(this); }
set { LastNameEntityProperty.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:
(Developer code)

140 | P a g e
IdeaBlade DevForce Property Interceptors

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

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.

C# [BeforeSet(EntityPropertyNames.LastName)]
public void UppercaseLastName(PropertyInterceptorArgs<Employee, String> args) {
var lastName = args.Value;
if ( !String.IsNullOrEmpty(lastName)) {
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.

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:

141 | P a g e
IdeaBlade DevForce Property Interceptors

C# // Note that no parameter follows the BeforeSet attribute


[BeforeSet]
public void BeforeSetAny(IPropertyInterceptorArgs args) {
if (!Thread.CurrentPrincipal.IsInRole("Administrator")) {
throw new InvalidOperationException("Only admistrators can change 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:

C# [AfterSet]
public void AfterSetAny(IPropertyInterceptorArgs args) {
LogChangeToEmployee(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:

C# [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
}
<AfterGet(EntityPropertyNames.LastName)> _
VB Public Sub UppercaseLastName(ByVal args As PropertyInterceptorArgs(Of Employee, String))

142 | P a g e
IdeaBlade DevForce Property Interceptors

''' … 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:

C# [BeforeSet(EntityPropertyNames.LastName, Order=-1.0)]
public void UppercaseLastName(PropertyInterceptorArgs<Employee, 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:

143 | P a g e
IdeaBlade DevForce Property Interceptors

C# [BeforeSet(EntityPropertyNames.FirstName)]
[BeforeSet(EntityPropertyNames.LastName)]
[BeforeSet(EntityPropertyNames.MiddleName)]
public void UppercaseName(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
If Not String.IsNullOrEmpty(name) Then
args.Value = args.Value.ToUpper()
End If
End Sub

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 actually to an inner class that is automatically generated inside each of the
DevForce Entity classes. Its primary purpose is to allow specification of property names as constants. Note that the
EntityPropertyNames class is defined as a partial class so that developers can add their own property names to the
class for any custom properties that they 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.

DataEntityPropertyGetInterceptorArgs<TInstance, TValue>
DataEntityPropertySetInterceptorArgs<TInstance, TValue>

144 | P a g e
IdeaBlade DevForce Property Interceptors

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


IPropertyInterceptorArgs
IdeaBlade.Core
IPropertyInterceptorArgs<TInstance, TValue>

PropertyInterceptorArgs<TInstance, TValue>

DataEntityPropertyInterceptorArgs<TInstance, TValue>
IdeaBlade.EntityModel
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

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.

145 | P a g e
IdeaBlade DevForce Property 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:

C# [AfterSet]
public void BeforeSetAny(IPropertyInterceptorArgs args) {
// Do not let any setters throw an exception
// Eat them and log them, and cancel the remainder of the set operation.
args.ExceptionAction = (e) => {
LogException(e);
args.Cancel = true;
};
}

VB <AfterSet> _
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) AnonymousMethod1(e, args)
End Sub

Private Function AnonymousMethod1(ByVal e As Object, ByVal args As IPropertyInterceptorArgs)


As Object
LogException(e)
args.Cancel = True
Return Nothing
End Function

146 | P a g e
IdeaBlade DevForce Property Interceptors

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

An example is shown below:

147 | P a g e
IdeaBlade DevForce Property Interceptors

C# [AfterSet]
public void AfterSetAny(IPropertyInterceptorArgs args) {
var entityPropertyArgs = args as IEntityPropertyInterceptorArgs;
if ( entityPropertyArgs != null) {
Log(“The “ + entityPropertyArgs.EntityProperty.Name + “ was set to the value: “ +
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

For a set operation, IdeaBlade has as part of its underlying implementation of any property the idea of possibly
validating ( verifying) the incoming data. The VerificationSetterOptions property of any implementation of
IEntityPropertySetInterceptorArgs provides the ability to determine whether a validation has or will be called
as well as allowing any „BeforeSet‟ code to actually change how the verification will occur.
public interface IEntityPropertySetInterceptorArgs : IEntityPropertyInterceptorArgs {
VerificationSetterOptions VerificationSetterOptions { get; set; }
}

An example:

C# [AfterSet]
public void BeforeSetAny(IEntityPropertySetInterceptorArgs args) {
// turn off validation
args.VerificationSetterOptions = VerificationSetterOptions.None;
}

VB <AfterSet> _
Public Sub BeforeSetAny(ByVal args As IEntityPropertySetInterceptorArgs)
' turn off validation
args.VerificationSetterOptions = VerificationSetterOptions.None
End Sub

148 | P a g e
IdeaBlade DevForce Property Interceptors

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:

C# [BeforeSet(EntityPropertyNames.HireDate)] // hire date is of type datetime


[BeforeSet(EntityPropertyNames.FirstName)] // firstname is of type string
public void StrangeAction(IPropertyInterceptorArgs args) {
var emp = (Employee) args.Instance;
var entityProperty = ((IDataEntityPropertyInterceptorArgs) args).EntityProperty;
.. do some very baroque operation with emp and entityProperty
}

VB <BeforeSet(EntityPropertyNames.HireDate), BeforeSet(EntityPropertyNames.FirstName)> _
Public Sub StrangeAction(ByVal args As IPropertyInterceptorArgs)
Dim emp = CType(args.Instance, Employee)
Dim entityProperty = (CType(args, IDataEntityPropertyInterceptorArgs)).EntityProperty
.. do some very baroque operation with emp and entityProperty
End Sub

But ideally we would prefer to write it like this, in order to avoid performing a lot of superfluous casts:

149 | P a g e
IdeaBlade DevForce Property Interceptors

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.

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 class and will locate any property interceptors defined there as well.
Example:

150 | P a g e
IdeaBlade DevForce Property Interceptors

C# public partial class Employee : IdeaBlade.EntityModel.Entity {

// internal class just for property interceptors


public class PropertyInterceptorsDefinitions {
[BeforeGet(Employee.EntityPropertyNames.LastName)]
public void LastNameInterceptor(IEntityPropertyInterceptorArgs args) {

}

[AfterSet]
public 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 Sub LastNameInterceptor(ByVal args As IEntityPropertyInterceptorArgs)

End Sub

<AfterSet> _
Public Sub LoggingInterceptor(ByVal args As IEntityPropertyInterceptorArgs)

End Sub
End Class

One 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:

C# public class UnattachedInterceptor {

[AfterSet(User.EntityPropertyNames.Name, TargetType=typeof(User)]
public void LoggingInterceptor(IEntityPropertyInterceptorArgs args) {

}
}

VB Public Class UnattachedInterceptor

<AfterSet(User.EntityPropertyNames.Name, TargetType:=GetType(User)> _
Public Sub LoggingInterceptor(ByVal args As IEntityPropertyInterceptorArgs)

End Sub
End Class

151 | P a g e
IdeaBlade DevForce Property Interceptors

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:
(Developer code)

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


(Developer code)

C# [AfterGet(EntityPropertyNames.LastName)]
public String UppercaseLastName(String lastName) {
if ( !String.IsNullOrEmpty(lastName)) {
return lastName.ToUpper();
} else {
return String.Empty;
}
}

VB <AfterGet(EntityPropertyNames.LastName)> _
Public Function UppercaseLastName(ByVal lastName As String) As String
If Not String.IsNullOrEmpty(lastName) Then
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:

152 | P a g e
IdeaBlade DevForce Property Interceptors

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

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;
}

153 | P a g e
IdeaBlade DevForce Property Interceptors

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 created via the PropertyInterceptorAction class and added to the
PropertyInterceptorManager.CurrentInstance as shown below:
(Developer code)

C# var piAction = new PropertyInterceptorAction<PropertyInterceptorArgs<Employee, String>>(


typeof(Employee),
Employee.LastNameEntityProperty.Name,
PropertyInterceptorMode.BeforeGet,
(args) => args.Value = arg.Value.ToUpper);
PropertyInterceptorManager.CurrentInstance.AddAction(piAction);

VB 'INSTANT VB TODO TASK: Assignments within expressions are not supported in VB.NET
'ORIGINAL LINE: var piAction = New PropertyInterceptorAction(Of PropertyInterceptorArgs(Of
Employee, String))(typeof(Employee), Employee.LastNameEntityProperty.Name,
PropertyInterceptorMode.BeforeGet, (args) => args.Value = arg.Value.ToUpper);
Dim piAction = New PropertyInterceptorAction(Of PropertyInterceptorArgs(Of Employee,
String))(GetType(Employee), Employee.LastNameEntityProperty.Name,
PropertyInterceptorMode.BeforeGet, Function(args) args.Value = arg.Value.ToUpper)
PropertyInterceptorManager.CurrentInstance.AddAction(piAction)

Interceptor actions can be removed in a similar manner.

This mechanism also allows the application of an interceptor action to a base class that is then, in turn, applied to all
of its subclasses. As a somewhat contrived example, you might want to completely disable all setters in an
application via a call like this:

C# var piAction = new PropertyInterceptorAction<PropertyInterceptorArgs<Employee, String>>(


typeof(Object), // everyone inherits from object
null, // no property name
PropertyInterceptorMode.BeforeSet,
(args) => throw new Exception(“No sets allowed”);
PropertyInterceptorManager.CurrentInstance.AddAction(piAction);
'INSTANT VB NOTE: This code snippet uses implicit typing. You will need to set 'Option Infer
VB On' in the VB file or set 'Option Infer' at the project level:

Dim piAction = New PropertyInterceptorAction(Of PropertyInterceptorArgs(Of Employee,

154 | P a g e
IdeaBlade DevForce Property Interceptors

String))(GetType(Object), Nothing, PropertyInterceptorMode.BeforeSet, Function(args) throw


New Exception(“No sets allowed”);
PropertyInterceptorManager.CurrentInstance.AddAction(piAction)

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:

C# Employee.AddressEntityProperty.SetterInterceptor.AddAction(
PropertyInterceptorTiming.Before,
args => args.Value = AddZipCode(args.Value));

VB 'INSTANT VB TODO TASK: Assignments within expressions are not supported in VB.NET
'ORIGINAL LINE:
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.

155 | P a g e
IdeaBlade DevForce Business Object Persistence

Business Object Persistence

Business Object Persistence


Note: Code Snippets in This Document
Object Persistence Overview
The Big Picture
DevForce and the ADO.NET EntityModel
Locating the Physical Data Source with a Key
Support for POCOs (Plain Old CLR Objects)
Persistence Management Capabilities
Retrieving business objects
The Entity Cache
Business objects in motion
Creating new business objects
Saving and undoing business object changes
Offline Support
Application Security
Business Object Security
N-Tier Architecture
Three-Tier Deployment
Model Choice by Configuration
Conclusion
Entity Queries and Entity Navigation
Entity Queries
Query v. Method Syntax
LINQ
The DevForce Predicate Builder
Example: Simulate an In() Clause Condition on a Distantly Related Entity
The PredicateDescription Class
Example: Given a Collection of Parent Entities, Retrieve the Related Children
PassthruESQL Queries
Remote Service Method Call (RSMC)
Entity Navigation
Parent-Child Navigation properties
Navigation Properties in Silverlight
Deferred Retrieval
Proactive Data Loads
Missing objects
The Null Entity
Asynchronous Communication with the Business Object Server
Asynchronous Queries
IAsyncResult Asynchronous Pattern
Asynchronous Fulfillment of Navigation Property Queries
Canceling Pending Operations
The EntityListManager
Entity Caching
All Business Objects are Cached
Entity Ancestry and Organization of the Cache
IdeaBlade DevForce Business Object Persistence

Business objects are unique in each cache


Entities in Lists
Business object proper, not the business object graph
Queries, Navigation, and the Cache
Query Cache
Primary key queries
“Object Not Found” and the Null Entity
Cache use when disconnected
Modifications
Stale Entity Data
Fetch Life Cycle Events
Query Workflow
Query Strategy
Fetch Strategies
MergeStrategies
InversionMode
Pre-Defined QueryStrategies
Custom QueryStrategies
DefaultQueryStrategy
When to Use The Different QueryStrategies
Making a One-Time Change to the QueryStrategy With Which a Given Query Is Run
Span Queries
Performance Details
Cached Entity Lifespan
Saving the Cache Locally
The TraceViewer: Watch What Data Is Being Loaded, and How
Using the Trace Viewer Stand-Alone
Embedding the Trace Viewer in Your Application
Embedding the WPFTraceViewer in Your WPF App
Embedding the WinTraceViewer in Your WinForms App
Getting Generated SQL to Display in the TraceViewer
Using the Trace Viewer With a Silverlight App
Creating Business Objects
When Not to Create
The Business Object Create Method
Generating unique identifiers
GUIDs
Sql Server Identity Ids
Custom id generation
Ids in mapping objects
Creating a valid business object
Auxiliary Business Object Class Methods
CompareTo()
ToString()
Adding and Removing Related Objects using Add() and Remove()
Business Object Creation Review
Saving Business Objects
EntityState of an Object
Undo
Multi-level Undo
Validation
Temporary Id Fix-up
Life Cycle Events
Client-Side Life Cycle Events
Saves and Transaction Management
IdeaBlade DevForce Business Object Persistence

Distributed Transactions
Re-query After Save
When Save Fails
SaveChanges() Exceptions
EntityManagerSaveException
SaveResult
Alternatives to Default SaveChanges Exceptions
Data Source Concurrency
Saving the “Dependency Graph”
Association Types
Compositions
Save the Root Entity
Saving Event Handler
Composition Business Rules
Concurrency Violations
Dependency Graph Retrieval
Workflow For a Save
Saving the Cache to a Local Disk File
XML Serialization of Business Objects

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.
IdeaBlade DevForce Business Object Persistence

Figure 4. 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 DevForce business objects. If the back-end data store is a relational database, the EntityServer leaves the direct
communication with the data store to the ADO.NET Entity Framework. However, if the back-end data store is a web
service, the DevForce EntityServer handles the job, since that capability does not exist within the 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 (besides handling those jobs against web services), mediates between the Entity Framework and
the DevForce EntityManager that manages the client-side cache used by your application.

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.
IdeaBlade DevForce Business Object Persistence

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. It also renders the object model in .NET code in a file named <ModelName>.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 s/he will use the Visual Studio Entity Data Model wizard to create the initial version of the
EDMX file and the corresponding generated code file. After that, he will work with some combination of the Visual
Studio Entity Model Designer and direct XML coding in the EDMX file, depending upon his preferences and
whether he needs to use features in his model that are not supported by the Entity Model designer.
The second step will be to create a Domain model using the DevForce Object Mapper. This model is so named
because it will be composed of one or more Entity Models persisted in .EDMX files.
The DevForce Object Mapper will alter the .EDMX file by adding additional elements and attributes. These added
features are ignored, and left undisturbed, by the ADO.NET Entity Data Model Designer. Because of this, the
developer can move back and forth between the Visual Studio Entity Model Designer and the DevForce Object
Mapper without fear of either disturbing the other‟s work.
There is, by intent, some overlap in the the functionality of the DevForce Object Mapper and ADO.NET Entity Data
Model Designer. Over time, this overlap may increase as we subsume additional aspects of the Entity Model
Designer‟s functionality. Our goal is to make it as convenient as possible for you to work with your model.
However, our intial work on the DevForce Object Mapper has focussed on providing needed or useful capabilities
that are either not present, or are difficult to work with, in the Entity Data Model Designer.
We mentioned that the Entity Data Model wizard and designer, in addition to altering the .EDMX file, generates the
classes that comprise the compilable manifestation of the object model. From the Object Mapper‟s enhanced version
of the .EDMX, DevForce generates two sets of classes. The first is essentially the same Entity Framework model
generated by the Visual Studio tools. This version of your object model will be deployed to the logical middle tier
of your application, where it is used by the ADO.NET Entity Framework for creating objects of the type that it
understands.
The second version of the object model generated by the DevForce Object Mapper is a DevForce version consisting
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”: unlike the Entity Framework
model, 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.

For those familiar with DevForce Classic (mated with .NET 2.0): the Entity Framework model essentially
takes over the function handled in DevForce Classic by the .ORM file. Both contain knowledge of the data
source and mapping information.

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:
IdeaBlade DevForce Business Object Persistence

Figure 5. 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 labelled 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 develop instructs the DevForce Object Mapper to regenerate code.
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
<DomainModelName>.<EntityModelName>.Designer.cs
(or .vb, if generated in Visual Basic rather than C#). For
example, the code file for the ServerModelNorthwindIB
Entity Data Model of a domain model named
DomainModel generated in C# would be named
DomainModel. ServerModelNorthwindIB.Designer.cs, as
shown at right.
If the domain model includes multiple Entity Models,
one such code file will be generated for each model, as
shown at left.

The partial class described in Figure 5 as “Developer-controlled” is optional, and can be generated by the Object
Mapper in a one-time operation, or hand-coded by the developer. In either case, once it exists, the Object Mapper
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 interceptors 19 to change the getter/setter behavior
of properties defined in the DevForce-controlled partial class.
If the developer asks the Object Mapper to generate
developer partial classes, it will generate one such
class for each type in the domain model. Each such
partial class will be generated into its own file, which
bears the name of the type. You can see this at right.

Again, these files are generated by the Object Mapper only when they do not already exist, and are not touched
subsequently. Thus the developer can safely add her own code to this file without fear that it will be overwritten.

19
See the Developers Guide chapter on “Property Interceptors”
IdeaBlade DevForce Business Object Persistence

If you are already familiar with the Entity Framework, you will note that DevForce code generation proceeds
according to the same pattern used by the Entity Framework. The Entity Framework also generates partial classes
for each type in a model, and all into a single class. It does not generate partial classes for developer work, but does
permit the developer to create and maintain such partial classes.

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 6. DevForce EntityManager Support for POCOs. The business entity you saw diagrammed
earlier in Figure 5. 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.
IdeaBlade DevForce Business Object Persistence

Figure 6. 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.
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
IdeaBlade DevForce Business Object Persistence

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 found 20. 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.
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.21
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.

20
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.
21
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.
IdeaBlade DevForce Business Object Persistence

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).
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.22
We explore these summary remarks with greater depth in the section “Saving Business Objects”.

22
DevForce also provides a facility known as “checkpointing” that provides a transaction facility for operations in the local
cache. Checkpointing gives you the ability to undo changes back to a specified state, perhaps not so far back as the state
when retrieved from the data source. The utility of “checkpointing” is most apparent in the UI so we cover it in the WinForm
User Interfaces chapter in the topic “Multi-Level Undo with Checkpoints”.
IdeaBlade DevForce Business Object Persistence

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.
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.
IdeaBlade DevForce Business Object Persistence

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


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 ServerFetching, ServerFetched, ServerSaving, and
ServerSaved events. You can handle the ServerFetching event to intercept data retrieval requests and the
ServerSaving event to intercept save requests, in each case before-the-fact, to make sure the authenticated user
has rights to do what she is requesting.
These handlers run server-side, and no client can prevent the server from invoking them. Furthermore, your handlers
can delegate their work to other methods that exist in libraries only deployed to the server. No hacker can examine
the latter, so your application can be made safe from disassembly and spoofing.
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:
6. Data source(s) on the data tier
7. EntityServer(s) as the data access tier
8. 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.
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
IdeaBlade DevForce Business Object Persistence

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.
The BOS monitoring console provides detailed data and global insight into the use (and abuse) of the application.

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.
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.
IdeaBlade DevForce Business Object Persistence

 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.

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 source23.
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. BasicQuerySyntaxQuery

C# var customersQuery =
from cust in _Em1.Customers
where cust.ContactTitle == "Sales Representative"
orderby cust.CompanyName

23
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.
IdeaBlade DevForce Business Object Persistence

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 syntax24 as


Code Snippet 2. 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)

Or just,
Code Snippet 3. 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. QueryWithExplicitlyTypedReturnValue

C# IEntityQuery<Customer> customersQuery = _em1.Customers

24
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.
IdeaBlade DevForce Business Object Persistence

.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. 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. ForceImmediateExecution

C# ICollection<Customer> customersQuery = _em1.Customers


.Where(c => c.ContactTitle == "Sales Representative")
.OrderBy(c => c.CompanyName)
.ToList();

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.
IdeaBlade DevForce Business Object Persistence

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”:

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:
IdeaBlade DevForce Business Object Persistence

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:

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. 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
IdeaBlade DevForce Business Object Persistence

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. 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 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
IdeaBlade DevForce Business Object Persistence

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:


DomainModelEntityManager em2 = new DomainModelEntityManager();
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((DomainModelEntityManager)null);

// Run the query against the assigned EntityManager, using that EntityManager's
// default QueryStrategy:
IEntityQuery<Customer> query7 = query0.With((QueryStrategy)null);
IdeaBlade DevForce Business Object Persistence

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
IdeaBlade DevForce Business Object Persistence

DevForce NullEntity for the target type. The NullEntity is a non-saveable, 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. 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()

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()
IdeaBlade DevForce Business Object Persistence

clause on the resultant query will specify a series of OR‟d key values. For example, consider the following
statements:
Code Snippet 11. 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]).Where(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. 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)

Code Snippet 13. 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);
IdeaBlade DevForce Business Object Persistence

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. 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

This can also be written in method-based syntax as


Code Snippet 15. BasicMethodSyntaxQuery (Repeated)

C# var customersQuery = _Em1.Customers


.Where(c => c.ContactTitle == "Sales Representative")
.OrderBy(c => c.CompanyName)
.Select(c => c);
IdeaBlade DevForce Business Object Persistence

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. 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 syntax 25, 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
 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.

25
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
IdeaBlade DevForce Business Object Persistence

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. 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. 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

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:
IdeaBlade DevForce Business Object Persistence

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. 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# p.ProductName.Contains(“Sir”)
/VB

...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:

C# Func<Product, bool>

VB Func(Of Product, Boolean)

Which we can generalize to:

C# Func<T, bool>

VB Func(Of T, Boolean)
IdeaBlade DevForce Business Object Persistence

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:
Code Snippet 20. 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
IdeaBlade DevForce Business Object Persistence

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. 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

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:
IdeaBlade DevForce Business Object Persistence

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. 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");
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
IdeaBlade DevForce Business Object Persistence

// 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

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.
IdeaBlade DevForce Business Object Persistence

Example: Simulate an In() Clause Condition on a Distantly Related Entity


Consider the following query:
Code Snippet 23. 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.
Code Snippet 24. 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);
}
}
IdeaBlade DevForce Business Object Persistence

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. PredicateDescriptions01

C# 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.


IdeaBlade DevForce Business Object Persistence

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. 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. CompositePredicateDescription

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. CompositePredicateDescriptionToLambda

C# using System.Linq.Expressions;

...

var exprFunc = (Expression<Func<Product, bool>>)p3.ToLambdaExpression();


var filterQuery = _em1.Products.Where(exprFunc);
IdeaBlade DevForce Business Object Persistence

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. 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
// 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))
IdeaBlade DevForce Business Object Persistence

' 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. 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

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"
IdeaBlade DevForce Business Object Persistence

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 cache26. 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 object27: 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.

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

26
We can extend some Passthru queries to search the cache. See “Advanced Business Object Concepts.”
27
RPC is not an “entity query” facility because it is not required to return entities.
IdeaBlade DevForce Business Object Persistence

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>.

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. NavigationBasic

C# Order anOrder = _em1.Orders.FirstOrNullEntity();


List<OrderDetail> lineItems = new List<OrderDetail>(anOrder.OrderDetails);
Console.WriteLine("lineItems.Count = {0}", lineItems.Count);

VB Dim anOrder As Order = _em1.Orders.FirstOrNullEntity()


Dim lineItems As New List(Of OrderDetail)(anOrder.OrderDetails)
Console.WriteLine("lineItems.Count = {0}", lineItems.Count)

We decide to increase the quantity ordered for the first OrderDetail as follows.

C# OrderDetail firstItem = lineItems[0];


firstItem.Quantity = 10;
IdeaBlade DevForce Business Object Persistence

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:

Code Snippet 33. NavigationBasicAsynchronous


IdeaBlade DevForce Business Object Persistence

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(EntityFetchedEventArgs<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.Result.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();
}
IdeaBlade DevForce Business Object Persistence

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()


IdeaBlade DevForce Business Object Persistence

…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 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(EntityFetchedEventArgs<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.Result.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


IdeaBlade DevForce Business Object Persistence

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(EntityFetchedEventArgs<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 EntityFetchedEventArgs(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:
IdeaBlade DevForce Business Object Persistence

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.
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?
IdeaBlade DevForce Business Object Persistence

We cannot know. Fortunately, we don‟t have to know28. 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

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

28
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.
IdeaBlade DevForce Business Object Persistence

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)
_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
IdeaBlade DevForce Business Object Persistence

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.
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.
IdeaBlade DevForce Business Object Persistence

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 it29. 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 class30. 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 now supports asynchronous versions of methods which communicate with the BOS. These
methods include:

 LoginAsync
 LogoutAsync
 ExecuteQueryAsync
 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 pattern31 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.

29
Like real cached entities, null entities must belong to a EntityManager and, in fact, are created by a EntityManager
30
This method is inherited from the root business object class, Entity.
31
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
IdeaBlade DevForce Business Object Persistence

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(EntityFetchedEventArgs<Customer> args) {


var resultList = args.Result;
Console.WriteLine("Query returned {0} entities", resultList.Count());
}

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 EntityFetchedEventArgs(Of Customer))


Dim resultList = args.Result
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.Result;
Console.WriteLine("Query returned {0} entities", resultList.Count());
},
token);
PromptToContinue();
}
IdeaBlade DevForce Business Object Persistence

The signature for the above queries is as follows:

C# public void ExecuteQueryAsync<T>(


IEntityQuery<T> query,
AsyncCompletedCallback<EntityFetchedEventArgs<T>> userCallback,
object userState
);

VB public void ExecuteQueryAsync(Of T)( _


IEntityQuery(Of T) query, _
AsyncCompletedCallback(Of EntityFetchedEventArgs(Of T)) userCallback, _
Object userState _
)

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 EntityFetchedEventArgs argument so she can distinguish one query from another. The
UserState object can be as simple as an integer, or it can be an arbitarily complex custom type.

Completed Queries
The EntityFetchedEventArgs parameter passed into an async query‟s callback method contains the following
members:

Property Description
Cancelled True if the query was canceled.
Cancellation of an async operation can be ordered by a call to
EntityManager.CancelAsync(), which takes a UserState object as a parameter.
Such cancellation only succeeds if the order is received in time. Note that
UserState objects must be unique across all async operations, whether queries,
saves, logins, or other.
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.
IdeaBlade DevForce Business Object Persistence

Query The IEntityQuery<T> object used in the asynchronous operation.


Result 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;
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 peration by calling EntityManager.CancelAsync(). We identify the
operation to cancel by passing in its identifying UserState. The operation stops at the next safe breaking point
before the operation finishes, if such a breaking point exists, and then invokes the callback method.
The caller can confirm that the query was successfully canceled by checking the Cancelled parameter of the
EventArgs object; it should read true.
IdeaBlade DevForce Business Object Persistence

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 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)
IdeaBlade DevForce Business Object Persistence

_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 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});
IdeaBlade DevForce Business Object Persistence

VB _employeeEntityListManager = New EntityListManager(Of Employee)(_em1, filter, _


New EntityProperty() {Employee.CityEntityProperty})

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
IdeaBlade DevForce Business Object Persistence

pOrder.Customer.Equals(_currentCustomer))
End Function

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. In DevForce 3.x, and in earlier
versions of DevForce, Entity inherited from System.Data.DataRow; but for several reasons, chief among which was
our desired to make our object model cross-compatible with Silverlight (which does not support DataSets), we have
replaced the DataSet and its component parts with our own set of storage classes.
Entity now lies at the base of the business object inheritance tree, inheriting nothing, though it does implement
several interfaces. These include:

 IdeaBlade.EntityModel.IEntityBase;

 System.ComponentModel.IEditableObject;

 System.ComponentModel.INotifyPropertyChanged; and

 SystemRuntime.InteropServices.IComparable.
Previously an Entity was a DataRow, and as such resided in a System.Data.DataTable, which in turn resided in a
System.Data.DataSet. Now an Entity lives in an IdeaBlade.EntityModel.EntityGroup which lives within an
EntityGroupCollection. You may find that you rarely need to interact directly with an EntityGroup or
IdeaBlade DevForce Business Object Persistence

EntityGroupCollection; 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.

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
IdeaBlade DevForce Business Object Persistence

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 instance32. 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.
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.

32
Multiple EntityManagers have their place but most applications will need only one. Multiple EMs are covered in
“Advanced Business Object Concepts”.
IdeaBlade DevForce Business Object Persistence

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.

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 search 33 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.

33
If we use the default QueryStrategy; we are just about to discuss QueryStrategy so bear with me.
IdeaBlade DevForce Business Object Persistence

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 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.

Fetch Life Cycle Events


DevForce raises the client-side Fetching event prior to performing a query and raises the client-side Fetched
event just before returning query results. We can listen to either or both by attaching a custom handler.
IdeaBlade DevForce Business Object Persistence

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 nothing34.
The Fetched 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 inversion35.
As previously discussed, there are corresponding server-side events named ServerFetching and
ServerFetched.

Query Workflow
Putting these points together, we can construct a schematic workflow for normal 36 DevForce entity queries and
entity navigation when the application is connected to the Business Object Server (BOS) running on its own
physical tier.

Table 2. Entity Query and Navigation Workflow When QueryStrategy = Normal

Component Action

Client Tier – The client application requests a particular


Application Code set of entities (the “desired entities”) either
by entity query or by entity navigation

Client Tier – Raises Fetching event. Listeners can see


EntityManager 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
are already in the client side cache.

Middle Tier - Business The BOS authenticates the client (the


Object Server currently logged in “user”) and runs any
developer-specified security checks in the
ServerFetching handler. If security

34
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
35
Span queries are later in this section. We cover “Query Inversion” in the “Advanced Business Object Concepts”.
36
The workflow is different in a few places when we use a different QueryStrategy. See the “QueryStrategy” topic under
“Advanced Business Object Concepts”.
IdeaBlade DevForce Business Object Persistence

checks fail, it raises a security exception


and sends this back to the client tier.

Middle Tier - Business If the data source If the data source is a


Object Server is a relational web service:
database:
The BOS converts
Having passed the query into
security checks, appropriate web
the BOS converts service calls and
the query into one submits them against
or more LINQ-to- the targeted service.
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 The data source performs the query or


Source queries and returns one or more result sets
back to the Business Object Server.

Middle Tier - Business If the data source If the data source is a


Object Server is a relational web service:
database:
The EntityServer
The Entity converts the result
Framework sets returned from the
converts the result data source into
sets returned from entities.
the data source
into ADO.NET
entities and
delivers them to
the EntityServer.

Middle Tier – Business The EntityServer repackages the entities


Object Server 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 After transmission, the BOS allows the


Object Server 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.
IdeaBlade DevForce Business Object Persistence

Client Tier – Client Tier –EntityManager: Compares


EntityManager 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 – Client Tier – Application Code: The


Application Code entities are available for processing.

The application developer may proceed blissfully unaware of all this effort.

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:
IdeaBlade DevForce Business Object Persistence

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.
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 3. 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.
IdeaBlade DevForce Business Object Persistence

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.
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.37 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.38
DevForce supports five different MergeStrategies: PreserveChanges, OverwriteChanges,
PreserveChangesUnlessOriginalObsolete, PreserveChangesUpdateOriginal, and NotApplicable. Their meanings
are shown in Table 4.
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 4. 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 Preserves the values in the Current state of the cached entity, if its
OriginalObsolete Original state matches the state retrieved from the datasource.

37
See the discussion on query inversion for more detail.
38
Conflicts are diagnosed by comparing the values in the entity‟s designated Concurrency column.
IdeaBlade DevForce Business Object Persistence

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,
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 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.
IdeaBlade DevForce Business Object Persistence

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 5. 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.

Queries That Cannot Be Inverted


The following types of queries cannot be inverted:
IdeaBlade DevForce Business Object Persistence

 A query that returns a scalar result. This includes all aggregate queries (Count, Sum, Avg, etc.). 39

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, 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 6.

39
Note that this group includes the example mentioned earlier in this discussion: “Give me the count of Customers located in
Idaho.”
IdeaBlade DevForce Business Object Persistence

Table 6. 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 instance40.

40
Immutable meaning that we can get the component fetch and merge strategies but we cannot reset them.
IdeaBlade DevForce Business Object Persistence

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.
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.
IdeaBlade DevForce Business Object Persistence

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. 41
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():
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():

41
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.
IdeaBlade DevForce Business Object Persistence

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.
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 source42.
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.

42
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.
IdeaBlade DevForce Business Object Persistence

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")

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!
IdeaBlade DevForce Business Object Persistence

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.
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.
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
IdeaBlade DevForce Business Object Persistence

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.
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:
IdeaBlade DevForce Business Object Persistence

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:
IdeaBlade DevForce Business Object Persistence

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();
IdeaBlade DevForce Business Object Persistence

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.

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.
IdeaBlade DevForce Business Object Persistence

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:
IdeaBlade DevForce Business Object Persistence

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:


IdeaBlade DevForce Business Object Persistence

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:
IdeaBlade DevForce Business Object Persistence

Here‟s some code for the startup program of a simple WinForm app that launches the WinForms TraceViewer
during initialization:
IdeaBlade DevForce Business Object Persistence

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:
IdeaBlade DevForce Business Object Persistence

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.
IdeaBlade DevForce Business Object Persistence

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 file43 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).

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...

43
In a development app with all parts running on a single machine, choose the App.Config file in the AppHelper project.
IdeaBlade DevForce Business Object Persistence

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 {

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) {
IdeaBlade DevForce Business Object Persistence

_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...

C# IdeaBlade.Core.TraceFns.WriteLine("Hello world!");

VB IdeaBlade.Core.TraceFns.WriteLine("Hello world!")
IdeaBlade DevForce Business Object Persistence

...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 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.
IdeaBlade DevForce Business Object Persistence

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.

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.
IdeaBlade DevForce Business Object Persistence

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.

Sql Server Identity Ids


DevForce detects Sql Server Identity columns and generates ids automatically. Its approach is almost exactly the
same as “custom id generation” which we‟ll discuss next. The key difference is that the id “seed” value – the source
of the next available id – is maintained by Sql Server rather than in a custom id seed table (e.g., “NextId”).
There are special consideration when using the DevForce Identity Id generator which are covered below.

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.
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.
IdeaBlade DevForce Business Object Persistence

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.
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 property44 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.

44
At least, there would be no such property generated by the DevForce Object Mapper
IdeaBlade DevForce Business Object Persistence

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


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
IdeaBladeTutorial 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.
IdeaBlade DevForce Business Object Persistence

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)

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. 45

45
The equivalent result on table rows in a relational database is that the child entity‟s foreign key value is changed.
IdeaBlade DevForce Business Object Persistence

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 parent46.

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 a composite primary key). 47
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 objects48. 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.

46
Speaking again of the equivalent result on table rows in a relational database, the child entity‟s foreign key value
is set to null.
47
See the appendix “Many-to-Many Associations in the Entity Framework” in the Object Mapping chapter for more information.
48
Note that those objects are not exposed in the conceptual model, and are never manipulated directly by you.
IdeaBlade DevForce Business Object Persistence

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.

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.
IdeaBlade DevForce Business Object Persistence

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.
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 source 49; 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.

Multi-level Undo
The EntityManager provides “Checkpoint” methods that facilitate implementation of applications that need multi-
level undo. The utility of “checkpointing” is most apparent in the UI so we cover it in the WinForm User Interfaces
chapter in the topic “Multi-Level Undo with Checkpoints”.

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:

49
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”.
IdeaBlade DevForce Business Object Persistence

 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.

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 7:
Table 7. EntityManager Life-Cycle Events

Event Typical Uses of the Corresponding Event Handler

Fetching Modify the query being submitted, or refuse the request for data.

Fetched 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.
IdeaBlade DevForce Business Object Persistence

The EntityManager raises the Fetched 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 list50. 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.

Server-Side Life Cycle Events


Server-side life cycle events on the EntityServer include ServerFetching, ServerFetched, ServerSaving,
and ServerSaved. These are summarized in Table 8.
Table 8. PersistenceServer Life-Cycle Events

Event Typical Uses of the Corresponding Event Handler

ServerFetching Modify the query being submitted, or refuse the request for data.

ServerFetched Modify the objects that were returned by the query

ServerSaving Modify the object submitted for saving, or refuse the request to
perform inserts and/or updates.

ServerSaved 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).

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.
For those familiar with DevForce Classic, the EntityServer.ServerFetching event replaces the DevForce Classic
PersistenceServer‟s QuerySecurityCheck event. Similarly, EntityServer.ServerSaving replaces
PersistenceServer.SaveSecurityCheck.
ServerFetching will have access both to the submitted query object and to an IPrincipal representing the
authenticated user who made the request. ServerFetched, ServingSaving, and ServerSaved will also have
access to that same IPrincipal, but instead of a query object they will have access to the full collection of DevForce
entities being retrieved or updated. Thus, the ServerFetched, ServingSaving, and ServerSaved event
handlers will make use of the copy of the Domainmodel assembly that has been deployed server-side.

50
SaveChanges() with no arguments, for example, is a blanket request to save every changed entity in cache.
IdeaBlade DevForce Business Object Persistence

Implementing Server-Side Life-Cycle Event Handlers

Unlike the client-side life-cycle events, the server-side Event Interface


events are handled by providing, for each, a class that
implements an appropriate interface. These interfaces ServerFetching IEntityServerFetching
reside in the IdeaBlade.EntityModel.Server assembly
(which must, of course, be referenced by the project that ServerFetched IEntityServerFetched
contains the life-cycle handlers), and are as shown in the
table at right. ServerSaving IEntity ServerSaving

ServerSaved IEntity ServerSaved

Once you have provided an implementation of the desired interface, you must attend to two additional steps to
ensure that the server-side methods can be found and used by DevForce:
1. Make sure that the assembly containing the implementations is listed as a top-level probe assembly in the
app.config file; and
2. Make sure that said assembly is deployed to the appropriate location at build time.
Here‟s an excerpt from an app.config file that lists an assembly named “Server” as a top-level probe assembly:

XML <ideablade.configuration version="5.00" updateFromDomainModelConfig="Ask"


loginManagerRequired="true">
<probeAssemblyNames>
<probeAssemblyName name="Server" />
</probeAssemblyNames>

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:
IdeaBlade DevForce Business Object Persistence

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 condition51.
The application relies upon the data source manager to provide two key benefits throughout the transaction:
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.

51
We cover save failures in topic coming up soon.
IdeaBlade DevForce Business Object Persistence

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 Transactions52.
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.

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.

52
At this writing, databases are the only DevForce supported data sources that support the X/Open XA protocol.
IdeaBlade DevForce Business Object Persistence

 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
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.
IdeaBlade DevForce Business Object Persistence

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.

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.
IdeaBlade DevForce Business Object Persistence

Data Source Concurrency


A multi-user application must decide how to resolve the conflict when two users try to update the same data source
entity53. 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.

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:

53
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.
IdeaBlade DevForce Business Object Persistence

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.54 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 do quite nicely; you can also use a GUID,

54
You can, of course, safely omit the primary key.
IdeaBlade DevForce Business Object Persistence

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 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.
IdeaBlade DevForce Business Object Persistence

 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 MergeStrategy55 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.
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 version56 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.

55
We discuss merge strategies in “Advanced Business Object Concepts”.
56
We cover entity versions in “Advanced Business Object Concepts”.
IdeaBlade DevForce Business Object Persistence

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).
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.
IdeaBlade DevForce Business Object Persistence

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 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.
IdeaBlade DevForce Business Object Persistence

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.57
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.
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.

57
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.
IdeaBlade DevForce Business Object Persistence

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.

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
IdeaBlade DevForce Business Object Persistence

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.
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:
IdeaBlade DevForce Business Object Persistence

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];

// 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)
IdeaBlade DevForce Business Object Persistence

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)

' 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 9. Transactional Save Workflow in an N-Tier Deployment

Component Action

Client Tier – Application The client application adds, modifies and deletes any number of business objects
IdeaBlade DevForce Business Object Persistence

Code 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 Builds map of data source-generated ids (e.g., for Identity columns). Calls method
Object Server 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 First the Saving event. This can be used to perform security checks on each entity
Object Server 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 If the data source is a relational If the data source is a web service:
Object Server database:
Converts the requests to the approprate
Forwards them to the Entity Framework web service calls and submits them to
for execution. the web service.

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 If the transaction failed, returns to the EntityManager the identity of the culprit
Object Server 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
reclaims them. This enables the object server to stay stateless.

Client Tier – Replaces cashed entities with updates from BOS. They are marked “unchanged”
EntityManager because they are now current.
Raises the Saved event with list of saved inserted and modified entities.

Client Tier – Application The application resumes.


IdeaBlade DevForce Business Object Persistence

Code

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
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:
IdeaBlade DevForce Business Object Persistence

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. 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()
IdeaBlade DevForce Business Object Persistence

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

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
IdeaBlade DevForce Business Object Persistence

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
IdeaBlade DevForce Business Object Persistence - Advanced

Business Object Persistence – Advanced

Business Object Persistence – Advanced


Getting Information About an Entity Type with GetEntityMeta()
Access Both Local and Remote Data Sources In the Same N-tier Application
Stored Procedure Queries
SQL Server Stored Procedure Queries
Stored Procedure Entity Navigation
Forced Re-fetch
Lost Connection During Query
Query Cache
EntityManager.RemoveEntities Overload Preserves Query Cache
MergeStrategy In More Detail
The EntityManager.AttachEntity Method
Filtering Queries
Query Inversion in More Detail
Transactional Queries
DevForce and Data Sources – Deep Dive
The Object Mapper and Manually Added or Modified Keys
DataSourceKeys, DataSourceKeyResolvers, and DataSourceExtensions
EntityManagers and DataSourceExtensions
Tenant Extensions
Multi-Part Extensions
Extensions and EntityServers
Dynamic DataSourceKeys and the DataSourceKeyResolver
Multiple Application Environments
Multi-Level Undo with Checkpoints
Multiple EntityManager Instances
Multi-Threading in a DevForce App
Batching Asynchronous Tasks
Service Oriented Architecture
POCO Support in DevForce
Examples of POCO Classes
Examples of a POCO Service Provider Class
Example of a Client-Side Class Containing Extension Methods for the EntityManager
Obtaining an EntityAspect Property on Your POCO Object
Data Contract Serializer (DCS) versus .NET Data Contract Serializer (NDCS)
POCO Save mechanisms
Summary – Things to Remember When Using POCOs in Your DevForce App
IdeaBlade DevForce Business Object Persistence - Advanced

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.Default.GetEntityMetadata(typeof(DomainModel.Employee));

VB

The EntityMetadata objects provides the following members:

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.
IdeaBlade DevForce Business Object Persistence - Advanced

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.

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.
IdeaBlade DevForce Business Object Persistence - Advanced

/// 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


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.
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 cache58. 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.

58
There is an advanced technique for applying a stored procedure query to the cache that we cover briefly in “Advanced
Business Object Concepts.”
IdeaBlade DevForce Business Object Persistence - Advanced

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.

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>
IdeaBlade DevForce Business Object Persistence - Advanced

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:

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);
}
IdeaBlade DevForce Business Object Persistence - Advanced

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:

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:
IdeaBlade DevForce Business Object Persistence - Advanced

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

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.Id59. 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 10 summarizes the situation.

Table 10. 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

59
In fact you can‟t do this within the Object Mapper for reasons we are now discussing.
IdeaBlade DevForce Business Object Persistence - Advanced

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.

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 11. PreserveChanges… strategies in a forced re-fetch

Strategy Description
PreserveChanges Replace unchanged entities but keep changed entities as they are.
PreserveChangesUnless Replace unchanged entities and changed entities that are obsolete (i.e., that
OriginalObsolete
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
IdeaBlade DevForce Business Object Persistence - Advanced

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 query60 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.

Query Cache
DevForce caches queries to improve performance 61. 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 Cache62. The second time it found the query
in the Query Cache and thus knew it could use apply the cache to the query instead.

60
This analysis applies to both entity query and entity navigation.
61
This analysis applies to both entity queries and entity navigation. Both use CacheFirstThenDataSource fetch strategy
by default.
IdeaBlade DevForce Business Object Persistence - Advanced

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.
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 query63? We will never have a query cache and we will always search the database. The performance of our
application will degrade

62
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).
63
This is not a rare scenario.
IdeaBlade DevForce Business Object Persistence - Advanced

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.

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
IdeaBlade DevForce Business Object Persistence - Advanced

Again, DevForce and the Entity Framework determine if our cached entity is current or obsolete based on the
original version of the property value.

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 property64 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 12. 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 Y ---- NC NC NC Y
OriginalObsolete

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.
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.

64
The possible values are Added, Deleted, Detached, Modified, and Unchanged. See “Data Row State” in the
glossary.
IdeaBlade DevForce Business Object Persistence - Advanced

An added cached entity must be deemed “obsolete” if it already exists in the data source 65. 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 13. 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 Y --- D Dt M
OriginalObsolete
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:
Table 14. 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 Y ---- NC NC NC
OriginalObsolete
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.

65
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.
IdeaBlade DevForce Business Object Persistence - Advanced

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 interpretation66. 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 removes 67 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.
 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 Added68.
In sum:
Table 15. 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 database 69.

66
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.
67
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..
68
An update would fail because there is no data source entity to update.
69
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”.
IdeaBlade DevForce Business Object Persistence - Advanced

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.
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”).
IdeaBlade DevForce Business Object Persistence - Advanced

 theEntity is presumed to exist in the persistent store; a subsequent change and save will translate to an
update statement.
 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)

 A "clone" is a copy of an entity, equivalent to calling the following:


IdeaBlade DevForce Business Object Persistence - Advanced

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

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:
IdeaBlade DevForce Business Object Persistence - Advanced

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);

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")));
IdeaBlade DevForce Business Object Persistence - Advanced

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.

var query = _Em1.Customers


C# .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 Orders70. 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!

70
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
IdeaBlade DevForce Business Object Persistence - Advanced

When a query cannot be inverted, a FetchStrategy other than DataSourceThenCache should be used. Table 16
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!

Table 16. 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:

var query = _Em1.Orders


C# .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().
IdeaBlade DevForce Business Object Persistence - Advanced

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:
IdeaBlade DevForce Business Object Persistence - Advanced

var query = em1.Customers


C# .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 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.
IdeaBlade DevForce Business Object Persistence - Advanced

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
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 web
services.
The DevForce Object Mapper does not do design-time access to databases. 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
associates an app.config file with the Entity Data Model (.edmx) file, placing it in the same project as the latter. That
app.config contains connection information to the database used by the EDM designer for its design work.
When you direct the DevForce Object Mapper to generate code, it creates (or updates) an instance of app.config in
the Visual Studio project where it stores the DomainModel (.ibedmx) file. In creating an edmKey element in the
IdeaBlade DevForce Business Object Persistence - Advanced

app.config for an Entity Data Model‟s database, the Object Mapper simply copies the connection information found
in the EDM‟s app.config.
Listing 4 shows the XML Schema element from an Entity Data Model (.edmx) file. Typically, this element is
generated initially by the EDM Designer, then modified slightly the DevForce Object Mapper. Note namespace and
two attributes prefixed with “ib”. These were written into the .edmx file by the DevForce Object Mapper. They are
respected by the EDM Designer, however, and it will not overwrite them even if you use it to generate fresh EDM
code later.
Listing 4. DataSourceKey attribute in the Entity Data Model (.edmx) file
<Schema Namespace="ServerModelNorthwindIB" Alias="Self"
XML xmlns="http://schemas.microsoft.com/ado/2006/04/edm"
xmlns:ib="http//www.ideablade.com/schemas/edmx"
...
ib:DataSourceKey="Default" ib:LastModTs="7/3/2008 12:54:54 PM">

When you save your work in the DevForce Object Mapper , it writes (subject to your okay) a similar connection
string into the app.config file that it saves in the DomainModel project. It writes this information as part of an
edmKey (for relational database sources) or a wsKey (for web service sources). Listing 5 shows the XML
statement written by the Devforce Object Mapper into its app.config for the same object model just referenced:
Listing 5. Data source identifier (edmKey) for run-time operations (written to the app.config file)

XML <ideaBlade.configuration version="5.00"


updateFromDomainModelConfig="Ask"
...
<edmKeys>
<edmKey
connection="metadata=res://ServerModelNorthwindIB/ServerModelNorthwindIB.csdl|res://Serve
rModelNorthwindIB/ServerModelNorthwindIB.ssdl|res://ServerModelNorthwindIB/ServerModelNor
thwindIB.msl;provider=System.Data.SqlClient;provider connection
string=&quot;Datasource=.;Initial Catalog=NorthwindIB;Integrated
Security=True;MultipleActiveResultSets=True&quot;"
containerName="ServerModelNorthwindIB.ServerModelNorthwindIBContext"
logTraceString="false" name="Default" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelNorthwindIB" />
</probeAssemblyNames>
</edmKey>
</edmKeys>
...
</ideaBlade.configuration>

Observe the connection information for the Entity Data Model and its datasource, and the DataSourceKeyName,
stored as the name attribute of the edmKey.
DataSourceKeys originally written by the Object Mapper into the app.config file, such as the one just shown, may
subsequently be altered or removed manually by the developer. Other DataSourceKeys may also be added
manually.
Why might a developer alter or add a DataSourceKey in app.config? The most common reason would be that he
wants to add keys that point to multiple variations of a particular Datasource (e.g., Development, Test, Production).
IdeaBlade DevForce Business Object Persistence - Advanced

The Object Mapper and Manually Added or Modified Keys


If your Domain Model project includes an existing app.config file and you add or modify Entity Data Models or
their DataSourceKey names inside the Object Mapper and save your work, the Object Mapper will ask if you want it
to update the app.config. Basically this update consists of modifying the edmKeys in the app.config file. The Object
Mapper will only modify edmKeys in the app.config file with names that match those that it displays in its designer,
and only if you accept its offer to do the update.

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 Datasource71. This key name is hard-coded into the business class at the time the latter is
generated by the Object Mapper.
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 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 (and possibly in wsKeys) that you configure in the app.config
file, by adding them to the name attribute of the edmKey or wsKey, 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");

71
You can find this on an entity instance as its EntityAspect.EntityMetadata.DataSourceKeyName property
IdeaBlade DevForce Business Object Persistence - Advanced

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
72
classes, which will be accessed by a given EntityManager.

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.

Now let‟s look at DataSourceKey names and extensions as they appear in edmKeys and wsKeys in an App.config
file. Listing 6 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. 73
Listing 6. 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;"
containerName="ServerModelNorthwindIB.ServerModelNorthwindIBContext"
logTraceString="false" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelNorthwindIB" />
</probeAssemblyNames>
</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

72
The default extension, incidentally, is no extension at all. If you create a new DevForce DomainModel and let the Object
Mapper write the edmKey entry into the configuration file, the DataSourceKey will be entered with a name of “Default”,
without an extension.
73
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.
IdeaBlade DevForce Business Object Persistence - Advanced

Catalog=AdventureWorks2000;Integrated Security=True;MultipleActiveResultSets=True&quot;"
containerName="ServerModelAw2000.ServerModelAw2000Context"
logTraceString="false" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelAw2000" />
</probeAssemblyNames>
</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;"
containerName="ServerModelNorthwindIB.ServerModelNorthwindIBContext"
logTraceString="false" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelNorthwindIB" />
</probeAssemblyNames>
</edmKey>

<edmKey name="Aw2000_Development"
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= DevelopmentDBMS_B;Initial
Catalog=AdventureWorks2000;Integrated Security=True;MultipleActiveResultSets=True&quot;"
containerName="ServerModelAw2000.ServerModelAw2000Context"
logTraceString="false" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelAw2000" />
</probeAssemblyNames>
</edmKey>

</edmKeys>

<wsKeys>
<wsKey url="http://api.google.com/search/beta2" endpointName="GoogleSearchPort"
name="GoogleSearch" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
</probeAssemblyNames>
</wsKey>
</wsKeys>

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. A wsKey is
present for a Google web service: the same service is used for Development and Production.
Instantiating an EntityManager and specifying a DataSourceKey Extension of “Release”...
IdeaBlade DevForce Business Object Persistence - Advanced

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; and data for classes mapped to
the Google web service would be accessed via the service addressable at the URL
http://api.google.com/search/beta2. 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 7. Extract of app.config file with multiple DataSourceKeys

XML <!-- Default databases -->

<edmKey name="NorthwindIB" connection="..."


containerName="ServerModelNorthwindIB.ServerModelNorthwindIBContext"
logTraceString="false" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelNorthwindIB" />
</probeAssemblyNames>
</edmKey>

<edmKey name="Aw2000" connection="..."


containerName="ServerModelAw2000.ServerModelAw2000Context"
logTraceString="false" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelAw2000" />
</probeAssemblyNames>
</edmKey>

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.
IdeaBlade DevForce Business Object Persistence - Advanced

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="... "


containerName="ServerModelAcmeTest.ServerModelAcmeTestContext"
logTraceString="false" tag="">
...
</edmKey>

<edmKey name="Acmetest2_SQLSRVR_OLE2" connection="..."


containerName="ServerModelAcmeTest.ServerModelAcmeTestContext"
logTraceString="false" tag="">
...
</edmKey>

<edmKey name="Acmetest2_SQLSRVR" connection="... "


containerName="ServerModelAcmeTest.ServerModelAcmeTestContext"
logTraceString="false" tag="">
...
</edmKey>

<edmKey name="Acmetest2" connection="..."


containerName="ServerModelAcmeTest.ServerModelAcmeTestContext"
logTraceString="false" tag="">
...
</edmKey>

</edmKeys>

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");


IdeaBlade DevForce Business Object Persistence - Advanced

…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.

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.
IdeaBlade DevForce Business Object Persistence - Advanced

To be more precise, it returns an object that implements IDataSourceKey. There are three
implementations of this interface at the moment, the EdmKey, the WsKey and the ClientEdmKey.
EdmKey and WsKey provide access and management information for relational database and web service
data sources, respectively. ClientEdmKey has no dependency on the Entity Framework on its data sources.

A DataSourceKeyResolver has a single method, GetKey(KeyName, KeyExtension) to do get this key. 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 a key in the IdeaBlade section of the application configuration file, App.config; it knows how to find the
requested key definition in the configuration file‟s XML and turn it into the appropriate kind of DataSourceKey.
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 application74. DevForce will prefer a loose version
of the file in the executable‟s directory. We make our change, drop it into the executable‟s 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.
 You are having trouble deploying a loose configuration file on IIS.

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
If in an assembly not already being probed by DevForce, add a top-level probe assembly tag to App.config so
DevForce can find it.

XML <edmKey
connection="metadata=res://ServerModelNorthwindIB/ServerModelNorthwindI
B.csdl|res://ServerModelNorthwindIB/ServerModelNorthwindIB.ssdl|res://S
erverModelNorthwindIB/ServerModelNorthwindIB.msl;provider=System.Data.S
qlClient;provider connection string=&quot;Data Source=.;Initial
Catalog=NorthwindIB;Integrated
Security=True;MultipleActiveResultSets=True&quot;"

containerName="ServerModelNorthwindIB.ServerModelNorthwindIBContext"
logTraceString="false" name="Default" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelNorthwindIB" />
</probeAssemblyNames>
</edmKey>

74
It is compiled into the application by default as an embedded resource of the AppHelper.dll.
IdeaBlade DevForce Business Object Persistence - Advanced

Add the following references to that project:


IdeaBlade.EntityModel
IdeaBlade.Core
IdeaBlade.EntityModel.WS // if creating WsKeys

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
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.
IdeaBlade DevForce Business Object Persistence - Advanced

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”75. 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.
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 information 76. If we execute in
development, we initialize the PM 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.

Multi-Level Undo with Checkpoints


Many applications could benefit from a robust, cross-entity undo feature.
Simple dialogs, for example, may present opportunities to modify several entities perhaps of different types. If the
user cancels, we have to reverse all those changes and restore the world to its pre-dialog state. There is a lot of
booking to do if we want to handle this manually.
Imagine a more complex case, a “New Account Wizard” in which the user steps through a series of screens, adding
a customer account, an address, some contacts, etc. In each step the user creates or modifies at least one business
object but often many more and of different entity types. The user may need to back up a step or two, discarding
changes page by page.
DevForce WinClient applications can set a “checkpoint” at each step and “roll-back” to an earlier step if the user
clicks “Cancel” or “Back.”
When the Wizard opens, we call “BeginCheckpoint” just before presenting the first page. DevForce WinClient
starts recording the user‟s changes as they affect entities in or entering the EntityManager cache. Such changes
could include:

75
“default”, not coincidentally, is the DevForce Object Mapper‟s default data source key name for the first data source.
76
Entities in the PM may map to more than one data source. The PM will suffix each data source key name with the same
extension.
IdeaBlade DevForce Business Object Persistence - Advanced

 Fetched entity
 Created entity
 Modified entity
 Entity undo
 Deleted entity
 Removed entity
 Re-attached entity
 Entity merged into the PM from another PM or EntitySet
If the user cancels a Wizard step, we call RollBackCheckpoint, and the EntityManager restores its entity cache
to the state when we began the checkpoint.
We can maintain a stack of checkpoints. We call BeginCheckpoint for step one; the user makes some changes
and proceeds to step 2 where we call BeginCheckpoint again. The EntityManager records a boundary in the
checkpoint log and increases the checkpoint “depth” by one. Now we can rollback either to the first or the second
checkpoint, depending upon how much activity we want to discard.
On the other hand, if the user presses “Ok” and completes the Wizard successfully, we can save all modified entities
and prevent rollbacks prior to the save point by calling EntityManager.SaveChanges().
We don‟t have to save the changes to close a checkpoint “session.” If we call ClearCheckPoints(), the
EntityManager discards the checkpoint log and stops logging entity cache activity. The user‟s changes are still
pending in cache – they are not in the database - but we have erased our checkpoints and can no longer rollback.
We might think of a DevForce WinClient checkpoint as an in-memory transaction along the lines of the more
familiar database transaction. The BeginCheckpoint, RollbackCheckpoint, and SaveChanges correspond
approximately to “Begin Transaction”, “Rollback Transaction”, and “Commit Transaction.” We can nest
checkpoints just as we nest database transactions. We can rollback to any pending checkpoint as we can rollback to
any pending transaction depth.
Why is there a ClearCheckPoints() method but no Commit() method? There is no “Commit” because we
feared a potentially fatal confusion. “Commit,” for most of us, implies a degree of permanence that an in-memory
transaction cannot match. We expect a durable modification of the database after a database “commit”. In contrast,
entity changes are still pending and tenuous after ClearCheckPoints(); they will be lost if the application
terminates before we persist them explicitly with SaveChanges().
We might regard a “checkpoint” as a kind of “snapshot”. A checkpoint differs from the everyday meaning of
“snapshot” in one key respect: a “checkpoint” records changes to the entity cache; a snapshot would record the
entire cache.
Checkpoints are comparatively lightweight. The snapshot of a large entity cache could hold thousands or millions of
entities while the equivalent checkpoint held only a few. A snapshot might be too large to hold in memory; a
checkpoint is compact and easily held in memory.
Checkpoints are efficient, especially for the SaveChanges and ClearCheckPoints operations; the
EntityManager just throws away the log. Rollback is a bit more expensive because the EntityManager must
reverse the logged changes; in most cases rollbacks are rare and the changes are few.
The new EntityManager checkpoint signatures are:
IdeaBlade DevForce Business Object Persistence - Advanced

Method Description
BeginCheckpoint() Start “checkpointing” (first call) or add a new checkpoint level
(subsequent calls). Returns the new checkpoint depth.
RollbackCheckpoint() Rollback one checkpoint. Restores the entity cache to its state one
checkpoint ago. The method returns the new checkpoint depth.
RollbackCheckpoint(int pCount) Rollback “pCount” number of checkpoints ago; returns the new
depth.
RollbackCheckpoints() Rollback all checkpoints and stops checkpointing. Restores the
entity cache to the state prior to the first checkpoint.
ClearCheckpoints() Stop checkpointing and discard the checkpoint log. The entity
cache remains in its current state. Roughly equivalent to a
“commit”.77
IsCheckpointing True if the EntityManager is maintaining checkpoints.
GetCheckpointDepth() Integer of the current checkpoint depth. The first
BeginCheckpoint is depth one; each subsequent call increases the
depth by one and each RollbackCheckpoint() reduces it by one.
The depth is zero when checkpointing stops.

A few points of additional interest:


 The scope of a checkpoint session is a single EntityManager.
 EntityManager.Clear(), like SaveChanges, clears the checkpoints and stops checkpointing.
 The checkpointing records changes to entity persistable state contained in the fields mapped to data source
columns. Checkpointing does not capture or restore data in custom fields that you may have added to your
business object‟s custom class.
 At this writing, the checkpoints are not included in the EntityManager‟s EntitySet nor are they
accessible directly as data. Therefore, we cannot preserve checkpoints when the application terminates and
restore them when we re-launch.

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.

77
Note that clearing a single checkpoint, or any number less than all of them, is not supported. Changes subsequent to a given
checkpoint may depend upon changes made after earlier checkpoints. It is therefore not possible to support the “commit” of
changes made since a more recent checkpoint while still permitting rollbacks to earlier checkpoints. If you clear checkpoints,
you must clear them all.
IdeaBlade DevForce Business Object Persistence - Advanced

Life with two PMs


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 PM „A‟ and PM „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.

Miscellaneous observations
Different EntityManagers do not interact. It is possible – and useful – to import entities from one PM 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 (Pm2) 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 PM 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 PM 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
IdeaBlade DevForce Business Object Persistence - Advanced

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
perspective of a given thread, it appears that operations on the object performed by different threads execute
78
sequentially (albeit in a nondeterministic order) rather than in parallel.

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
GetEntitiesAsync() 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. You can read about asynchronous queries in the DevForce Developers Guide (in the chapter on
Object Persistence), and see sample code in the Asynchronous Queries instructional unit that is shipped with the
product.
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 in .NET 2.0. Multi-threading is still hard. The BackgroundWorker made the
syntax easy: it did not make good multi-threaded design easier!
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.

78
Excerpted from ”Characterizing Thread Safety” by Brian Goetz, available on the web at:
http://www-128.ibm.com/developerworks/java/library/j-jtp09263.html
IdeaBlade DevForce Business Object Persistence - Advanced

 Never communicate entities across thread boundaries. If the caller must know about some entities, send a
list of PrimaryKeys 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() {

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
IdeaBlade DevForce Business Object Persistence - Advanced

// 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.
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.
IdeaBlade DevForce Business Object Persistence - Advanced

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 three respects. First, DevForce applications are .NET applications, which
means it is very easy to build web services into the application. Second, you can map a DevForce business object to
a Web service, which means that web service entities are first class entities like table, view, and stored procedure
entities. Third, 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 79.
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.

79
Web service entities and Web service wrappers for the business object model are in the alpha bits at this writing. Please
contact IdeaBlade for more recent information about these important features.
IdeaBlade DevForce Business Object Persistence - Advanced

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 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 service80.
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