553
p Developer Guide Version 5.2.5 DevForce

IdeaBlade DevForce Developers Guide 5.2.5

  • Upload
    talynone

  • View
    1.227

  • Download
    14

Embed Size (px)

DESCRIPTION

IdeaBlade DevForce Developers Guide 5.2.5

Citation preview

Page 1: IdeaBlade DevForce Developers Guide 5.2.5

p

Developer Guide Version 5.2.5

DevForce

Page 2: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Developers Guide Contents

2 | P a g e

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

Page 3: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Developers Guide Contents

3 | P a g e

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

Page 4: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Developers Guide Contents

4 | P a g e

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

Page 5: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Developers Guide Contents

5 | P a g e

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

Page 6: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Developers Guide Contents

6 | P a g e

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

Page 7: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Developers Guide Contents

7 | P a g e

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

Page 8: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

8 | P a g e

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.

Page 9: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

9 | P a g e

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

Page 10: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

10 | P a g e

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.

Page 11: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

11 | P a g e

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.

Page 12: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

12 | P a g e

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.

Page 13: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

13 | P a g e

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.

Page 14: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

14 | P a g e

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

Page 15: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

15 | P a g e

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.

Page 16: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

16 | P a g e

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.

Page 17: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

17 | P a g e

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.

Page 18: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

18 | P a g e

DevForce Silverlight1

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.

Page 19: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

19 | P a g e

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

Page 20: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

20 | P a g e

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

Page 21: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

21 | P a g e

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.

Page 22: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

22 | P a g e

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.

Page 23: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

23 | P a g e

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.

Page 24: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

24 | P a g e

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

Page 25: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

25 | P a g e

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:

Page 26: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

26 | P a g e

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

Page 27: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

27 | P a g e

automatically store a user‟s pending changes locally every few minutes “just in case.” If the application crashes or

the battery dies, the user could re-launch later and recover her work.

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

preserves the cache locally, shuts down, re-launches while disconnected, does work, saves that work locally, and

finally saves the pending changes to the database when reconnected.

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

risk is greater when the time from change to save is prolonged. The response is essentially the same: DevForce

detects the concurrency violation and the application resolves it, perhaps with the user‟s help.

Page 28: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

28 | P a g e

More DevForce Advantages

We‟ve seen the DevForce capabilities that are most critical for enterprise application development.

There are other ways in which DevForce improves upon the 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.

Page 29: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

29 | P a g e

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

Page 30: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

30 | P a g e

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.

Page 31: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

31 | P a g e

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.

Page 32: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

32 | P a g e

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.

Page 33: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

33 | P a g e

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.

Page 34: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce and the Entity Framework

34 | P a g e

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.

Page 35: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Getting Started

35 | P a g e

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

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

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:

Page 36: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Getting Started

36 | P a g e

Documentation Menu Item

Description

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.

Page 37: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Getting Started

37 | P a g e

Tools Menu Item Description Applies to DevForce Silverlight

Assembly Binding

Redirector Discovers third-party control suites on your machine; compares the

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.

No

Config Editor Edits an IdeaBlade.ibconfig file governing your application‟s

deployment. DevForce application deployment is its own chapter in this

guide.

Yes

Database Installer Installs the NorthwindIB sample database. This is a SQL Server

database (not a database server) that is used in many of the Learning

Units that accompany the product.

Yes

N-Tier

Configuration

Starter

Quick „n dirty tool that facilitates testing a distributed app. Creates

folders for client- and server-side assemblies and configuration files,

and otherwise facilitates running the DevForce EntityService in a

separate process from the client side app and EntityManager.

No

Product Key

Updater Facilitates replacing your current product key with a new one (in the

case of upgrade, etc.)

Yes

Tool Box Installer When you elect installation of Windows Forms support (the default),

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 3rd

party control suite for which

we have a dedicated visual component. This installer will help you

(re)install these components.

No

Trace Viewer Tool for listening to logged activity from a running DevForce

application.

Yes

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.

Page 38: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Getting Started

38 | P a g e

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.

Page 39: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Getting Started

39 | P a g e

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.

Page 40: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Getting Started

40 | P a g e

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.

Page 41: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

41 | P a g e

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.

Page 42: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

42 | P a g e

Figure 1. Application Components in a DevForce Application

One of them, called an EntityServer, moves data (and data requests) between the ADO.NET Entity Framework

and DevForce business objects. 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).

Page 43: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

43 | P a g e

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

Page 44: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

44 | P a g e

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.

Page 45: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

45 | P a g e

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

Page 46: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

46 | P a g e

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

Page 47: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

47 | P a g e

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

Page 48: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

48 | P a g e

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.

Page 49: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

49 | P a g e

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;

Page 50: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

50 | P a g e

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:

Page 51: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

51 | P a g e

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.

Page 52: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

52 | P a g e

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.

Page 53: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

53 | P a g e

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.

Page 54: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

54 | P a g e

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.

Page 55: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

55 | P a g e

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

Page 56: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

56 | P a g e

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.

Page 57: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

57 | P a g e

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;

Page 58: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

58 | P a g e

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.

Page 59: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

59 | P a g e

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:

Page 60: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

60 | P a g e

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

Page 61: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

61 | P a g e

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.

Page 62: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

62 | P a g e

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

Page 63: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

63 | P a g e

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;

Page 64: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

64 | P a g e

}

}

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"

Page 65: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

65 | P a g e

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

Page 66: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

66 | P a g e

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

}

Page 67: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

67 | P a g e

}

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:

Page 68: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

68 | P a g e

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.

Page 69: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

69 | P a g e

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.

Page 70: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

70 | P a g e

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:

Page 71: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

71 | P a g e

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

Page 72: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

72 | P a g e

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>

Page 73: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

73 | P a g e

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

Page 74: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

74 | P a g e

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!

Page 75: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

75 | P a g e

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.

Page 76: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

76 | P a g e

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.

Page 77: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

77 | P a g e

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

Page 78: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

78 | P a g e

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:

Page 79: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

79 | P a g e

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;

Page 80: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

80 | P a g e

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

Page 81: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

81 | P a g e

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.

Page 82: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

82 | P a g e

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:

Page 83: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

83 | P a g e

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:

Page 84: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

84 | P a g e

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:

Page 85: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

85 | P a g e

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

Page 86: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

86 | P a g e

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

Page 87: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

87 | P a g e

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

Page 88: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

88 | P a g e

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.

Page 89: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

89 | P a g e

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.

Page 90: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

90 | P a g e

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.

Page 91: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

91 | P a g e

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

Page 92: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

92 | P a g e

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]

Page 93: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

93 | P a g e

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.

Page 94: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

94 | P a g e

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>

Page 95: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Hello DevForce

95 | P a g e

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.

Page 96: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Class Libraries

96 | P a g e

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.

Page 97: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Class Libraries

97 | P a g e

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

VB

aCustomer.EntityAspect

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.

Page 98: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Class Libraries

98 | P a g e

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.

Page 99: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Class Libraries

99 | P a g e

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.

Page 100: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Class Libraries

100 | P a g e

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.

Page 101: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Class Libraries

101 | P a g e

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.

Page 102: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Class Libraries

102 | P a g e

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.

Page 103: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Class Libraries

103 | P a g e

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:

Page 104: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Class Libraries

104 | P a g e

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.

Page 105: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

105 | P a g e

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

Page 106: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

106 | P a g e

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.

Page 107: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

107 | P a g e

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.

Page 108: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

108 | P a g e

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.

Page 109: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

109 | P a g e

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.

Page 110: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

110 | P a g e

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.

Page 111: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

111 | P a g e

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

Page 112: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

112 | P a g e

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:

Page 113: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

113 | P a g e

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.

Page 114: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

114 | P a g e

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

Page 115: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

115 | P a g e

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.

Page 116: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

116 | P a g e

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

Page 117: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

117 | P a g e

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.

Page 118: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

118 | P a g e

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.

Page 119: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

119 | P a g e

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:

Page 120: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

120 | P a g e

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

Page 121: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

121 | P a g e

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

Page 122: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

122 | P a g e

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

Page 123: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

123 | P a g e

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

Page 124: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

124 | P a g e

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:

Page 125: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

125 | P a g e

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

Page 126: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

126 | P a g e

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.

Page 127: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

127 | P a g e

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>

Page 128: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

128 | P a g e

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

Page 129: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

129 | P a g e

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

Page 130: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

130 | P a g e

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

EntityPropertyChanged

Before{PropertyName}Set

After{PropertyName}Set

EntityChanging

EntityChanged

BeforeSet

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

Page 131: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

131 | P a g e

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 model15

.

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.

Page 132: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

132 | P a g e

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.

Page 133: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

133 | P a g e

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 database16

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

Page 134: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

134 | P a g e

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

Page 135: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

135 | P a g e

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:

Page 136: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

136 | P a g e

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.

Page 137: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

137 | P a g e

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

Page 138: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

138 | P a g e

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:

Page 139: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Mapping

139 | P a g e

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.

Page 140: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

140 | P a g e

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)

Page 141: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

141 | P a g e

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:

Page 142: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

142 | P a g e

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

}

VB <AfterGet(EntityPropertyNames.LastName)> _

Public Sub UppercaseLastName(ByVal args As PropertyInterceptorArgs(Of Employee, String))

Page 143: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

143 | P a g e

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

Page 144: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

144 | P a g e

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>

Page 145: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

145 | P a g e

NavigationEntityPropertyGetInterceptorArgs<TInstance, TValue>

NavigationEntityPropertySetInterceptorArgs<TInstance, TValue>

The boldfaced characters above indicate whether we are providing interception to a get or a set property, as well as

whether we are intercepting a data or a navigation property.

In general, you can write an interception method with an argument type that is any base class of the actual argument

type defined for that interceptor. If you do use a base class, then you may need to perform runtime casts in order to

access some of the additional properties provided by the specific subclass passed in at runtime. These subclassed

properties will be discussed later.

The entire inheritance hierarchy for property interceptor arguments is shown below:

Assembly Where Defined Property Interceptor Arguments

IdeaBlade.Core IPropertyInterceptorArgs

IPropertyInterceptorArgs<TInstance, TValue>

PropertyInterceptorArgs<TInstance, TValue>

IdeaBlade.EntityModel DataEntityPropertyInterceptorArgs<TInstance, TValue>

DataEntityPropertyGetInterceptorArgs<TInstance, TValue>

DataEntityPropertySetInterceptorArgs<TInstance, TValue>

NavigationEntityPropertyInterceptorArgs<TInstance, TValue>

NavigationEntityPropertyGetInterceptorArgs<TInstance, TValue>

NavigationEntityPropertySetInterceptorArgs<TInstance, TValue>

The generic <TInstance> argument will always be the type that the intercepted method will operate on, known

elsewhere in this document and the interceptor API as the “TargetType”. The <TValue> argument will be the type

of the property being intercepted. i.e. „String‟ for the „LastName‟ property.

Note that the interceptor arguments defined to operate on DevForce entities break into multiple subclasses with

additional associated interfaces based on two primary criteria.

1) Is it a „get‟ or a „set‟ interceptor?

a. „get‟ interceptor args implement IEntityPropertyGetInterceptorArgs

b. „set‟ interceptor args implement IEntityPropertySetInterceptorArgs

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.

Page 146: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

146 | P a g e

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

Page 147: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

147 | P a g e

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:

Page 148: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

148 | P a g e

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

Page 149: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

149 | P a g e

The DevForce EntityProperty is an abstract class with two concrete subclasses; a DataEntityProperty and a

NavigationEntityProperty ( discussed elsewhere in this guide). The next two IEntityPropertyInterceptorArgs

subinterfaces allow access to instances of one or the other of these depending on whether the property being

intercepted is a data or a navigation property.

C#

public interface IDataEntityPropertyInterceptorArgs : IEntityPropertyInterceptorArgs {

DataEntityProperty DataEntityProperty { get; }

}

VB Public Interface IDataEntityPropertyInterceptorArgs

Inherits IEntityPropertyInterceptorArgs

ReadOnly Property DataEntityProperty() As DataEntityProperty

End Interface

C#

public interface INavigationEntityPropertyInterceptorArgs : IEntityPropertyInterceptorArgs {

NavigationEntityProperty NavigationEntityProperty { get; }

}

VB

Public Interface INavigationEntityPropertyInterceptorArgs

Inherits IEntityPropertyInterceptorArgs

ReadOnly Property NavigationEntityProperty() As NavigationEntityProperty

End Interface

IPropertyInterceptorArgs Type Coercion

One of the first issues that a developer will encounter with writing interceptor actions that handle more than one

property is that it becomes difficult or impossible to use a concrete subtype as the argument to the interceptor.

For example, imagine that we wanted to write a single action that handled two or more very different properties each

of a different type:

This could be written as follows:

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:

Page 150: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

150 | P a g e

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:

Page 151: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

151 | P a g e

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

Page 152: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

152 | P a g e

Alternative PropertyInterceptor Attribute Method Signatures

While the property interceptor methods described previously allow a great deal of control over the entire

interception process, there are times when this is overkill. Sometimes all you really want is to do is modify or

inspect the incoming or outgoing values. In these cases, a simplified signature for an interception method is also

provided.

For example the following standard interceptor action:

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

Page 153: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

153 | P a g e

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;

}

Page 154: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

154 | P a g e

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

VB 'INSTANT VB NOTE: This code snippet uses implicit typing. You will need to set 'Option Infer

On' in the VB file or set 'Option Infer' at the project level:

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

Page 155: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Property Interceptors

155 | P a g e

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.

Page 156: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 157: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 158: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 159: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 160: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 161: IdeaBlade DevForce Developers Guide 5.2.5

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 interceptors19

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”

Page 162: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 163: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 164: IdeaBlade DevForce Developers Guide 5.2.5

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 found20

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

Page 165: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 166: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 167: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 168: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 169: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 170: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 171: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 172: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 173: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 174: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 175: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 176: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 177: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 178: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 179: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 180: IdeaBlade DevForce Developers Guide 5.2.5

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 syntax25

, 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

Page 181: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 182: IdeaBlade DevForce Developers Guide 5.2.5

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#

/VB

p.ProductName.Contains(“Sir”)

...is a predicate that examines a product and returns true if the product‟s ProductName contains the “Sir” string.

The CLR type of the predicate in our example is:

C#

Func<Product, bool>

VB

Func(Of Product, Boolean)

Which we can generalize to:

C#

Func<T, bool>

VB

Func(Of T, Boolean)

Page 183: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 184: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 185: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 186: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 187: IdeaBlade DevForce Developers Guide 5.2.5

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

}

}

Page 188: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 189: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 190: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 191: IdeaBlade DevForce Developers Guide 5.2.5

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"

Page 192: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 193: IdeaBlade DevForce Developers Guide 5.2.5

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;

Page 194: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 195: IdeaBlade DevForce Developers Guide 5.2.5

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

}

Page 196: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 197: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 198: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 199: IdeaBlade DevForce Developers Guide 5.2.5

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?

Page 200: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 201: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 202: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 203: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 204: IdeaBlade DevForce Developers Guide 5.2.5

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

}

Page 205: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 206: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 207: IdeaBlade DevForce Developers Guide 5.2.5

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)

Page 208: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 209: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 210: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 211: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 212: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 213: IdeaBlade DevForce Developers Guide 5.2.5

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 search33

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.

Page 214: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 215: IdeaBlade DevForce Developers Guide 5.2.5

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 normal36

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 – Application Code

The client application requests a particular

set of entities (the “desired entities”) either

by entity query or by entity navigation

Client Tier – EntityManager

Raises Fetching event. Listeners can see

the query and, optionally, cancel the query.

Checks if it can satisfy the query with the

entities in the client-side cache. If so, it

returns them immediately; end of

workflow.

If not, the EntityManager sends the

query along with authentication

information to the Business Object Server

(BOS) on the middle tier. It may modify

the request before sending to the BOS if it

can determine that some of desired entities

are already in the client side cache.

Middle Tier - Business Object Server

The BOS authenticates the client (the

currently logged in “user”) and runs any

developer-specified security checks in the

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

Page 216: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence

checks fail, it raises a security exception

and sends this back to the client tier.

Middle Tier - Business Object Server

If the data source

is a relational

database:

Having passed

security checks,

the BOS converts

the query into one

or more LINQ-to-

Entities queries in

the form expected

by the ADO.NET

Entity Framework.

If a relational

database is the

data source, the

Entity Framework

converts the LINQ

to Entities query

into one or more

SQL queries and

submits them to

the data source

query mechanism.

If the data source is a

web service:

The BOS converts

the query into

appropriate web

service calls and

submits them against

the targeted service.

Data source – Data Source

The data source performs the query or

queries and returns one or more result sets

back to the Business Object Server.

Middle Tier - Business Object Server

If the data source

is a relational

database:

The Entity

Framework

converts the result

sets returned from

the data source

into ADO.NET

entities and

delivers them to

the EntityServer.

If the data source is a

web service:

The EntityServer

converts the result

sets returned from the

data source into

entities.

Middle Tier – Business Object Server

The EntityServer repackages the entities

obtained from the data source into a format

that can be transmitted efficiently. It then

ships the entity data to the client side

application.

Middle Tier – Business Object Server

After transmission, the BOS allows the

server‟s local copy of the entities to go out

of scope and the garbage collector reclaims

them. This enables the BOS to stay

stateless.

Page 217: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence

Client Tier –EntityManager

Client Tier –EntityManager: Compares

fetched entities to entities already in the

cache. Adds new entities to the cache.

Replaces matching cached entities that are

unmodified (in essence refreshing them).

Preserves cached entities with pending

modifications because the query strategy is

normal.

Client Tier –EntityManager: Reapplies the

original query to the cache to locate all

desired entities.

Client Tier –EntityManager: Raises the

Fetched event. Listeners can examine the

list of entities actually retrieved from the

data source.

Client Tier –EntityManager: Returns the

desired entities to the application.

Client Tier – Application Code

Client Tier – Application Code: The

entities are available for processing.

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

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:

Page 218: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 219: IdeaBlade DevForce Developers Guide 5.2.5

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 OriginalObsolete

Preserves the values in the Current state of the cached entity, if its

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.

Page 220: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 221: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 222: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 223: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 224: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 225: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 226: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 227: IdeaBlade DevForce Developers Guide 5.2.5

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!

Page 228: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 229: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 230: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 231: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 232: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 233: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 234: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 235: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 236: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 237: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 238: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 239: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 240: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 241: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 242: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 243: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 244: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 245: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 246: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 247: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 248: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 249: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 250: IdeaBlade DevForce Developers Guide 5.2.5

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 source49

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

Page 251: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 252: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 253: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence

Implementing Server-Side Life-Cycle Event Handlers

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

events are handled by providing, for each, a class that

implements an appropriate interface. These interfaces

reside in the IdeaBlade.EntityModel.Server assembly

(which must, of course, be referenced by the project that

contains the life-cycle handlers), and are as shown in the

table at right.

Event Interface

ServerFetching IEntityServerFetching

ServerFetched IEntityServerFetched

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:

Page 254: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 255: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 256: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 257: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 258: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 259: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 260: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 261: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 262: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 263: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 264: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 265: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 266: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 267: IdeaBlade DevForce Developers Guide 5.2.5

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)

Page 268: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 269: IdeaBlade DevForce Developers Guide 5.2.5

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 Object Server

Builds map of data source-generated ids (e.g., for Identity columns). Calls method

on instance of developer‟s id generation class with remaining temporary. This

method returns a map of temporary-to-permanent ids which the BOS returns to the

client tier.

Client –EntityManager Uses the temp-to-perm id map to replace all temporary ids.

Transmits the save list to the BOS.

Middle Tier – Business Object Server

First the Saving event. This can be used to perform security checks on each entity

in the save list. If a security check fails, an exception can be thrown back to the

EntityManager (or any other desired action taken.) Workflow ends.

Otherwise…

Constructs a batch of insert, update, and delete operations, adjusted for optimistic

concurrency checking as required.

Arranges them by type per the prescribed PersistenceOrder.

Middle Tier – Business Object Server

If the data source is a relational

database:

Forwards them to the Entity Framework

for execution.

If the data source is a web service:

Converts the requests to the approprate

web service calls and submits them to

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 Object Server

If the transaction failed, returns to the EntityManager the identity of the culprit

entity and the exception raised by the data source. The EntityManager stores this

information in the SaveResult and returns to the client application. Workflow ends.

Otherwise…

The transaction succeeded. The BOS re-queries the database(s) for all of the

inserted and modified entities that are sourced in databases, thus capturing the

effects of triggers that fired during save.

Converts the (potentially) revised data into entities and sends them to the client side

EntityManager.

The server‟s local copy of the entities go out of scope and the garbage collector

reclaims them. This enables the object server to stay stateless.

Client Tier – EntityManager

Replaces cashed entities with updates from BOS. They are marked “unchanged”

because they are now current.

Raises the Saved event with list of saved inserted and modified entities.

Client Tier – Application The application resumes.

Page 270: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 271: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 272: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 273: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 274: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 275: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 276: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 277: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 278: IdeaBlade DevForce Developers Guide 5.2.5

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>

Page 279: IdeaBlade DevForce Developers Guide 5.2.5

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

}

Page 280: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 281: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 282: IdeaBlade DevForce Developers Guide 5.2.5

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 OriginalObsolete

Replace unchanged entities and changed entities that are obsolete (i.e., that

would fail an optimistic concurrency check if saved now).

PreserveChangesUpdateOriginal Replace unchanged entities. Keep changed entities and make them current

if they are obsolete by updating their original versions.

Custom Navigation property with Forced Re-fetch

Navigation properties execute according to strategy prescribed by the EntityManager.

DefaultQueryStrategy. The default is Normal. We can change it dynamically but the Normal strategy is the

best default choice for most applications so let‟s assume we leave it that way.

The first time we call the navigation property the PM will get the entities from the data source and put them in the

cache. The next time, and every subsequent time, the navigation property will look in the cache first and find the

entities there. So during the entire user session these entities may never be refreshed.

This is great for a list of states but not so great for more volatile entities such as theater seats.

Some developers will be tempted to override the navigation property to get fresh data from the data source every

time. The following is a typical example that strives to keep the Customer.Orders ultra-current:

C#

public override ReadOnlyEntityList<Order> Orders {

get {return base.Orders.

ForceRefetch(MergeStrategy.Overwrite);}

}

VB

Public Overrides ReadOnly Property Orders() As _

IdeaBlade.Persistence.ReadOnlyEntityList(Of Order)

Get

Return MyBase.Orders.ForceRefetch(MergeStrategy.Overwrite)

End Get

End Property

Page 283: IdeaBlade DevForce Developers Guide 5.2.5

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 performance61

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

Page 284: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 285: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 286: IdeaBlade DevForce Developers Guide 5.2.5

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 OriginalObsolete

Y ---- NC NC NC Y

N OW OW OW OW Y

PreserveChangesUpdateOriginal Y or N NC NC NC NC Y

NC = No change; preserve the current version values of the cached entity

OW = Overwrite the cached entity‟s current version values with data from the data source entity

Post Current = „Y‟ means the cached entity is “current” relative to the data source after the merge.

There are important artifacts not immediately observable from this table.

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.

Page 287: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

An added cached entity must be deemed “obsolete” if it already exists in the data source65

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

Y --- D Dt M

N U U U U

PreserveChangesUpdateOriginal Y or N M D Dt M

A = Added, D = Deleted, Dt = Detached, M = Modified, U = Unchanged

The merge may change the original version of a changed cached entity to match the data source values.

PreserveChanges never touches the original version.

The original version is always changed with the OverwriteChanges strategy.

It is reset with the PreserveChangesUnlessOriginalObsolete strategy if (and only if) the entity is

obsolete..

PreserveChangesUpdateOriginal updates the original version (but not the current version!) if the

entity is obsolete. This step ensures that the cached entity appears current while preserving the pending

changes.

These effects are summarized in the following table:

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 OriginalObsolete

Y ---- NC NC NC

N OW OW OW OW

PreserveChangesUpdateOriginal Y or N OW OW OW OW

Merging when the cached entity is not in the data source

We begin by considering cached entities that are unchanged. If the query applied to the cache returns an unchanged

entity, „X‟, and the query applied to the data source did not return its mate, we can safely assume that „X‟ was

deleted after we fetched it. We can remove „X‟ from the cache.

We turn next to changed cached entities where we must distinguish between a query that tests only for the primary

key and one that tests for something other than the primary key.

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.

Page 288: IdeaBlade DevForce Developers Guide 5.2.5

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 removes67

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 database69

.

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

Page 289: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 290: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 291: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 292: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 293: IdeaBlade DevForce Developers Guide 5.2.5

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.

C# var query = _Em1.Customers

.Where(c => c.CustomerID == "CONSH")

.SelectMany(c => c.Orders);

QueryStrategy aQueryStrategy =

new QueryStrategy(FetchStrategy.DataSourceThenCache,

MergeStrategy.PreserveChanges, InversionMode.On);

query.QueryStrategy = aQueryStrategy;

foreach (Order anOrder in query) {

System.Diagnostics.Debug.WriteLine(anOrder.OrderDate.ToString());

}

Assert.IsTrue(query.ToList().Count > 0, "should return orders");

VB

In our initial run, we have the InversionMode set to On. Because DevForce is unable to invert the query, a

QueryInversionServerException is thrown, with the following message:

This query is not automatically invertible and cannot be executed

unless either its QueryInversionMode is set to 'Manual' or its

FetchStrategy is set to Optimized, DataSourceOnly or CacheOnly.

If we change the InversionMode to Try and rerun the query, it runs without an exception, but the Assert test fails,

because no Orders were included in the result set. Why? Because changing the InversionMode from On to Try

didn‟t alter the fact that the query couldn‟t be inverted; it just told DevForce not to worry about that fact. The result

set returned with a FetchStrategy of DataSourceThenCache is only that obtained in a final query against the cache,

after entities retrieved from the data source have been placed there. Since the query was not invertible, no Customer

objects were retrieved into the cache, and that final query returns an empty result.

Suppose now we set the FetchStrategy to DataSourceAndCache. Now references to the Order objects retrieved from

the data source are included in the result set. A second application of the query, this time against the cache, may or

may not pick up additional 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

Page 294: IdeaBlade DevForce Developers Guide 5.2.5

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:

C# var query = _Em1.Orders

.Where(o => o.Customer.CustomerID == "CONSH");

VB

This form of the query, unlike the other one, is invertible.

A Special Case: Using the Skip() Method on an EntityQuery

The query below uses the DataSourceOnly QueryStrategy in combination with a call to Skip().

Page 295: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 296: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

C# var query = em1.Customers

.Where(c => c.Orders

.Any(o => o.OrderDate.HasValue == true &&

o.OrderDate.Value.Year == 1997));

VB

This query targets Customers but must process Orders to find the correct set of Customers. DevForce would have no

difficulty inverting this query, but suppose you submitted it with an InversionMode of Off and a FetchStrategy of

DataSourceThenCache. The InversionMode setting would mean that only Customer objects were retrieved into the

cache: no Order objects. “Great!” you say. “That‟s all I wanted: Customers.” But even though you have the desired

Customers in your cache, you don‟t yet have references to them.

How does DevForce get these references? Because of the FetchStrategy you specified, DevForce now resubmits

your query, this time against the cache; and the set of references to Customer objects that it will return will be

entirely determined by the Customers that meet the query criteria when the query is resubmitted against the cache.

But wait! There is no guarantee that the cache contains the same Order objects that were found in the data source; it

will, in fact, contain no Order objects at all unless some other, unrelated operation that was previously executed

caused some to be retrieved. Therefore the set of Customers found by the query when submitted against the cache

may be very different from the set found when it was submitted against the data source. Indeed, the set may be

empty. You may get references to no Customers or some Customers, but there is no guarantee, and indeed little

likelihood, that you‟ll get references to all of the Customers retrieved by your query from the data source.

If, on the other hand, you submitted your query with a FetchStrategy of DataSourceAndCache, you‟ll get want you

wanted: all Customers in the data source who meet your conditions, as well as all Customers that exist only in your

local cache that meet those conditions. With that FetchStrategy, DevForce performs a union of the references

obtained by the two query submissions.

The DataSourceAndCache FetchStrategy does have some drawbacks which we‟ll discuss momentarily. Generally

speaking, it is the appropriate FetchStrategy only in the following circumstance:

1. Your query will use related objects;

2. You want to include in the result set references to entities that exist in the cache but which have not yet

been persisted to the database;

3. DevForce can‟t invert the query; and

4. You can‟t write an equivalent query that is invertible.

The reason that DataSourceThenCache is the preferred FetchStrategy for other circumstances is that, under certain

circumstances, DataSourceAndCache can produce confusing results. Suppose you have some Customer objects in

the cache, including Customer XYZ, and you submit a DataSourceAndCache query for Customers with Orders in

the current year. Customers meeting this condition are fetched from the data source into the cache, and merged there

with Customers already residing in the cache with a 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.

Page 297: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 298: IdeaBlade DevForce Developers Guide 5.2.5

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

XML <Schema Namespace="ServerModelNorthwindIB" Alias="Self"

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

Page 299: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 300: IdeaBlade DevForce Developers Guide 5.2.5

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

classes, which will be accessed by a given EntityManager. 72

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.

Page 301: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 302: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 303: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 304: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 305: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 306: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 307: IdeaBlade DevForce Developers Guide 5.2.5

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 information76

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

Page 308: IdeaBlade DevForce Developers Guide 5.2.5

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:

Page 309: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 310: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 311: IdeaBlade DevForce Developers Guide 5.2.5

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

sequentially (albeit in a nondeterministic order) rather than in parallel. 78

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

Page 312: IdeaBlade DevForce Developers Guide 5.2.5

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

Page 313: IdeaBlade DevForce Developers Guide 5.2.5

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.

Page 314: IdeaBlade DevForce Developers Guide 5.2.5

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 model79

.

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.

Page 315: IdeaBlade DevForce Developers Guide 5.2.5

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

In addition to objects based on classes inheriting from IdeaBlade.EntityModel.Entity and generated by the DevForce

Object Mapper, you can now use Plain Old CLR Objects (POCOs) with DevForce. The class for your object must be

deployed on both the client and the server and must be contained in one of the assemblies routinely searched by

80 This decision does not prevent us from exposing the business object model as a Web service later. Even then we could retain

our remoting interface within the application.

Page 316: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

DevForce (e.g., the one with the DomainModel) or in an assembly explicitly mentioned in the Probe Assemblies

section of the app.config (and/or web.config) file at the top level (rather than in an EdmKey).

An additional class (or classes), a “POCO service provider”, must be supplied server-side, marked with special

attributes. This class will contain methods to perform the server-side data retrieval for your custom objects. The

names for the data retrieval methods may either conform to flexible naming conventions (to be discussed below), or

they can be named anything you desire and marked with an attribute that specifies that their exact name must be

used in the client-side data retrieval statement.

For convenience, you will probably also want to deploy a client-side class containing extension methods for the

EntityManager, so that you can refer to your POCO objects in LINQ queries in a manner similar to that which you

use with your DevForce Entities.

If the objects have an identified (primary) key value, they will operate fully as first-class citizens in the DevForce

local cache. That means, among other things, that they can be:

Stored in the cache;

Queried against and retrieved from the cache;

Updated in the cache;

Created in the cache; and of course

Saved from the cache.

Objects that do not have an identified key can still be retrieved, but will not be stored in the DevForce cache.

DevForce‟s support for POCO objects follows Microsoft RIA (Rich Internet Application) standards, and uses RIA

attributes and naming conventions, to the extent that those are available. As those standards and facilities evolve or

are fleshed out, the implementation in DevForce will be enhanced or migrated to maintain maximum compatibility

with the RIA standards.

Examples of POCO Classes

Here is a class representing a State of the United States. At runtime, you might, for example, creating instances of

this class using data from an XML data file deployed on the server:

Page 317: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

C#

using System;

using System.Collections.Generic;

using System.Linq;

using System.ComponentModel.DataAnnotations;

namespace AppWithPocos.Pocos {

public class State {

public State() {

}

//[Key]

public string Abbrev {

get {

return _abbrev;

}

set {

_abbrev = value;

}

}

public string Name {

get {

return _name;

}

set {

_name = value;

}

}

public bool Lower49 {

get {

return _lower49;

}

set {

_lower49 = value;

}

}

public long Population {

get {

return _population;

}

set {

__population = value;

}

}

#region Private Fields

string _abbrev;

string _name;

bool _lower49;

long _population;

#endregion Private Fields

}

}

Page 318: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

Examples of a POCO Service Provider Class

This POCO Service Provider class can be named anything – and you may have many such classes – but must be

flagged with the attribute [EnableClientAccess]. All such classes will be deployed and used server-side only.

C#

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.ComponentModel;

using System.Text;

using IdeaBlade.EntityModel;

using IdeaBlade.Core;

using System.Reflection;

using IdeaBlade.Core.DomainServices;

using AppWithPocos.Pocos;

using System.Xml;

using System.ComponentModel.DataAnnotations;

using System.Runtime.Serialization;

namespace AppWithPocos {

[DataContract]

[KnownType (typeof(List<State>))]

[EnableClientAccess]

public class PocoServiceProvider {

public PocoServiceProvider() { }

#region State

//[RequiresAuthentication] // Uncomment this attribute will require "real"

authentication

public IEnumerable<State> GetStates() {

IEnumerable<State> states = ReadStatesData("states.xml");

return states;

}

private static IEnumerable<State> ReadStatesData(string fileName) {

// Create an isntance of XmlTextReader and call Read method to read the file

XmlTextReader textReader = new XmlTextReader(fileName);

textReader.Read();

List<State> states = new List<State>();

while (textReader.Read()) {

State aState = new State();

textReader.MoveToElement();

if (textReader.Name == "State") {

aState.Abbrev = textReader.GetAttribute("Abbrev").Trim();

aState.Name = textReader.GetAttribute("Name").Trim();

aState.Lower49 =

Convert.ToBoolean(Convert.ToInt32(textReader.GetAttribute("Lower49")));

states.Add(aState);

}

}

return (IEnumerable<State>)states;

}

Page 319: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

#endregion State

}

}

Note the “GetStates()” method. This method retrieves the data requested in a query (which is usually submitted from

the client). It follows a naming convention similar to the one supported under RIA services, where the name consists

of a prefix that is one of a number of synonyms for “retrieve” (here, “Get”) and a suffix that is the pluralized name

of a POCO type (here, “States”). The prefixes may be any of the following:

Get Query

Fetch Retrieve

Find Select

When the above naming convention is used, a query can be constructed client-side with an expression such as the

following:

C#

new EntityQuery<State>("States", anEntityManager);

Alternatively, the method can be adorned with the QueryAttribute.

C#

[Query]

public IEnumerable<State> ReturnAllStates() {

IEnumerable<State> states = ReadStatesData("states.xml");

return states;

}

In that case, when called client-side the exact name used for the query must be supplied:

C#

new EntityQuery<State>("ReturnAllStates", anEntityManager);

Query Methods with Parameters

Methods with parameters are supported as well. For example, an additional overload to the GetStates() method

above that only returns states with a population size greater than a size passed in might be written as shown below.

(Naturally, this would require that the internal ReadStatesData() method be modified as well.)

C#

public IEnumerable<State> GetStates(long minPopulationSize) {

IEnumerable<State> states = ReadStatesData("states.xml", minPopulationSize);

return states;

}

Page 320: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

On the client side, parameters may be specified by using one of the EntityQuery.AddParameter() overloads. The

following snippet calls the parameterized GetStates() method to return just those states with a population greater

than one million people:

C#

var query = new EntityQuery<State>("States", anEntityManager);

query.AddParameter(1000000);

// see

Any number of query parameters are permitted, and the standard .NET overload resolution rules apply. This means

that the order and type of the parameters are checked to find appropriate matches, with type coercion occurring as

required.

When to Use Parameterized Query Methods Rather Than LINQ Expressions

The following two queries will return the same results:

C#

var query1 = new EntityQuery<State>("States", anEntityManager);

query.Where(state => state.Population > 1000000);

var query2 = new EntityQuery<State>("States", anEntityManager);

query.AddParameter(1000000);

Furthermore, in both cases, the restriction to those states with greater than one million population occurs on the

server, not the client. So the question arises: is one to be preferred over the other? The answer usually depends

upon how the server-side method itself is implemented.

In general, unless the server-side method can internally use the query parameter to restrict its own query against

some back-end data store, query parameters have no advantage over LINQ query restrictions. In fact, LINQ queries

are far more flexible and intuitive to work with under most circumstances. Nevertheless, there will be cases where a

back-end data store‟s ability to optimize some queries will yield sufficient performance improvement to justify the

use of query parameters.

For example, consider the Windows file system‟s ability to search for files, given a path and wildcards. While the

same result could be accomplished via a server-side method that returned all of the files in the file system and then

iterated over them to locate a desired set of files, it would likely be faster to call the file system directly with the path

and wildcard restrictions.

Example of a Client-Side Class Containing Extension Methods for the

EntityManager

The following class, deployed client-side, can provide further help in putting your POCO objects on an equal footing

with your DevForce-generated objects. First, look at the class; then below, we‟ll show what its presence permits in

terms of data retrieval syntax:

C#

using System;

using System.Collections.Generic;

using System.Linq;

//using System.Web;

using System.Diagnostics;

using System.Text;

using System.Linq.Expressions;

Page 321: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

using IdeaBlade.EntityModel;

using IdeaBlade.EntityModel.Extensions;

using IdeaBlade.Core;

//using AppWithPocos.Pocos;

using DomainModel;

using AppWithPocos.Pocos;

namespace AppWithPocos {

public static class EmExtensions {

#region State

public static EntityQuery<State> States(this EntityManager em) {

return new EntityQuery<State>("States", em);

}

#endregion State

}

}

Because there are now static property named “States” and “Foos” available that return EntityQuery<State> and

EntityQuery<Foo>, respectively, you can now order the retrieval of States and Foos with the following syntax:

C#

_mgr.ExecuteQueryAsync<State>(_mgr.States()

.Where(s => s.Lower49, GotStates, null);

This is very similar to what you do with DevForce entities, except that, since States() is an extension method, so you

will have to include the parentheses -- _mgr.States() -- which you do not have to do when referencing the generated

properties of the EntityManager that return EntityQuery<T>.

Note that you can extend the queries with clauses with the full set of LINQ extension methods -- such as Where(),

used above -- just as you can do with ordinary queries.81

Obtaining an EntityAspect Property on Your POCO Object

Objects from DevForce-generated Entity classes have an EntityAspect property through which a number of

important operations and useful pieces of metadata can be obtained. Custom objects can benefit from the same

facilities by one of two methods:

Implementing a very simple interface, IHasEntityAspect, or

Wrapping them at runtime

The latter method preserves their DevForce-ignorant POCO status, but will not provide equal performance with the

former method.

Getter and Setter can be placeholders in the IHasEntityAspect implementation:

C#

amespace AppWithPocos.Pocos {

81 As of DevForce 5.1.1, the Include() syntax on a POCO entity query is not yet implemented. The call will compile but will not

yet do anything. This will be corrected in a later release.

Page 322: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

public class State:IHasEntityAspect {

…[snip]

#region IHasEntityAspect Members

public EntityAspect EntityAspect {

get;

set;

}

#endregion

To wrap an Entity at runtime for the purpose of getting at the EntityAspect property, call the static method

EntityWrapper.Wrap():

C#

var stateWithAspect = EntityWrapper.Wrap(aState);

Having done that, you can then ask questions like the following…

C#

Bool isModified = stateWithAspect.EntityAspect.IsModified();

…and of course, use any of the other facilities of EntityAspect. (Those are documented in the Developers Guide

chapter “Class Libraries”.)

Data Contract Serializer (DCS) versus .NET Data Contract Serializer

(NDCS)

The .NET framework includes several important serialization classes (which are described, for reference, in the

callout at the end of this section). For the purpose of your DevForce apps, two of these are of particular importance:

the Data Contract Serializer (DCS) and the .NET Data Contract Serializer (NDCS).

The NetDataContractSerializer (NDCS) is used by all types of .NET apps except Silverlight apps, for which only the

DataContractSerializer (DCS) is available. Since the DCS requires more from you in the way of data annotations

than the NDCS, and since some of you will be writing business object classes that you will want to use in both

Silverlight and WFP or WinForm apps, we have provided an option in DevForce to force the use of the DCS. By

setting this option you can be sure that you business object classes will work properly in both environments

(Silverlight v. non-Silverlight).

With DCS you may need to attribute your POCO classes with so they will be serializable. The need to be so arises

when your POCO class does not have a property attributed as a key. Here‟s a POCO class that does have key:

Page 323: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

C#

public class State {

public State() {

}

[Key] public string Abbrev {

get {

return _abbrev;

}

set {

_abbrev = value;

}

}

public string Name {

get {

return _name;

}

set {

_name = value;

}

}

}

VB

When your POCO class is defined with a key, DevForce assumes that it will need to be serialized at some point and

performs the necessary type inference. When your class is defined without a key, DevForce makes no such

inference, and you will have to let it know explicitly that it needs to be serialized.

You can do this in two ways. The preferred way for most usages is to attribute the class with the DevForce

DiscoverableType attribute:

C# Using IdeaBlade.EntityModel;

[DiscoverableType(DiscoverableTypeMode.KnownType)] public class State {

public State() {

}

public string Abbrev {

get {

return _abbrev;

}

set {

_abbrev = value;

}

}

public string Name {

get {

return _name;

}

set {

_name = value;

}

Page 324: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

}

}

VB

A second method is to implement the (empty) interface IKnownType:

C# Using IdeaBlade.EntityModel;

public class State {

public State() : IKnownType { }

public string Abbrev {

get {

return _abbrev;

}

set {

_abbrev = value;

}

}

public string Name {

get {

return _name;

}

set {

_name = value;

}

}

}

The first method is preferable, but the second method can be useful if you will have many classes that inherit from a

base class, and you wish only to “mark” the base class.

Page 325: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

Attributing a Type Where the Signature of Any DataMember-Attributed Member

Indicates a Different Type Than Is Actually Returned

The KnownType attribute is used by the DCS to mark up a type with information about any of its members marked

with a [DataMember] attribute where the declared type of the member is different from the actual type of the

member.

Suppose, for example, that your State class includes a Cities property which is typed as an object but actually returns

a List<Cities>. This will confuse the serializer and make it serialize the property improperly. To enable the property

to be serialized properly, you must attribute it as follows:

.NET Serialization Classes (for reference)

The following is taken from Martin Konicek‟s discussion at http://coding-time.blogspot.com/2008/03/serialize-object-graph-to-xml-in-net.html .

XmlSerializer

Cannot serialize circular references.

If more objects point to the same object, its copies are created in the xml for each of these references.

Has to know all types that could be encountered during serialization in advance - throws an exception on unknown type.

Known types are passed in the constructor to XmlSerializer or marked by XmlIncludeAttribute.

Generates simple readable xml.

DataContractSerializer

Has to know types in advance - like XmlSerializer.

Can serialize circular references - construct with preserveObjectReferences = true

Used by WCF.

NetDataContractSerializer - better!

Serializes object graph properly including circular references.

Doesn't have to know types in advance.

However, MSDN states that it can be only used in .Net <-> .Net communication, which is ok also for storing object in a file.

Generates simple readable xml.

BinarryFormatter

Works well, like NetDataContractSerializer.

Disadvantage is that it serializes to binary format, which make its unusable e.g. for saving to a file that user could later edit.

The output is smallest thanks to the binary format.

SoapFormatter

Deprecated. Cannot serialize generic collections

All serializers need the type to be serialized marked by SerializableAttribute.

Page 326: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

C#

[DataContract]

[KnownType(typeof(List<City>)] public class State {

public State() {}

[... snip]

public object Cities {

get {

return _cities;

}

set {

_cities = value;

}

}

#region Private Fields

List<City> _cities;

#endregion Private Fields

}

POCO Save mechanisms

DevForce provides two different mechanisms for saving POCO objects. These will be referred to as either adapter-

based or convention-based implementations. By default, IdeaBlade will attempt to locate an adapter-

basedimplementation; if unsuccessful, it will then look for a convention-based implementation.

The implementation mechanism can also be specified explicitly by setting the PocoSaveMode property of the

SaveOptions instance passed into the EntityManager.SaveChanges call:

C# var so = new SaveOptions();

so.PocoSaveMode = PocoSaveMode.UseEntitySaveAdapter;

myEntityManager.SaveChanges(so);

Page 327: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

The PocoSaveMode is an enumeration with the following structure:

C#

[/// <summary> /// Determines how to discover any custom server side POCO save methods.

/// </summary>

public enum PocoSaveMode {

/// <summary>

/// Use an EntitySaveAdapter subclass if found, otherwise use save methods discovered

via convention.

/// </summary>

Default = 0,

/// <summary>

///

/// </summary>

UseEntitySaveAdapter = 1,

/// <summary>

///

/// </summary>

UseMethodsDiscoveredViaConvention = 2

}

Adapter-Based Saves

The adapter-based mechanism requires the existence of a server-side class that inherits from the

IdeaBlade.EntityModel.Server.EntitySaveAdapter. If such a class is found, a single instance of this class will be

created for each SaveChanges call, and the appropriate methods corresponding to each insert, update, or delete will

be called for each entity to be saved. A single null-parameter constructor is required.

Note that a single insert, update, or delete method handles saves for every entity type, so if save logic needs to be

different for different types, the type of the entity passed in will need to be inspected and the execution branched

appropriately.

The following bare-bones version of an EntitySaveAdapter implementation shows the methods you have available

to override:

Page 328: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

C#

public class PocoSaveAdapter : EntitySaveAdapter {

public PocoSaveAdapter() {

}

public override void BeforeSave(

System.Collections.IEnumerable entities, SaveOptions saveOptions) {

}

public override void AfterSave() {

}

public override void InsertEntity(object entity) {

}

public override void UpdateEntity(object entity, object originalEntity) {

}

public override void DeleteEntity(object entity) {

}

}

Convention-Based Saves

The convention-based mechanism requires the existence of a server-side class with either the

[DiscoverableType(DiscoverableTypeMode.ServerService)] attribute or alternatively (for RIA services

compatibility), the [EnableClientAccess] attribute.82

This class will include insert, update, and delete methods

named according to the conventions defined below. It must also include a single null-parameter constructor.

If such a class is found, a single instance of it will be created for each SaveChanges call, and the appropriate

methods corresponding to each insert, update, or delete will be called for each entity to be saved.

For each type for which a persistence operation is provided, up to three methods must be written. The name of the

method must begin with one of the prefixes defined below. The method must also conform to the signature defined

below.

If you do not expect to save objects that require one or more of these signatures, then they do not have to be

implemented. In other words, if you never plan to delete “Foo” type objects, then you do not need to implement the

Delete method.

Insert Method

Prefixes: Insert, Add, or Create

Signature: void InsertX(T entity), where T is an entity Type

Update Method

Prefixes: Update, Change, or Modify

Signature: void UpdateX(T current, T original), where T is an entity Type

Delete Method

Prefixes: Delete or Remove

Signature: void DeleteX(T entity), where T is an entity Type

82 DevForce recognizes either attribute, as a convenience for those porting their app from RIA services

Page 329: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Persistence - Advanced

Below is an example implemention showing the use of the required conventional method names and signatures.

InsertFoo could equally be named “AddFoo” or “CreateFoo”; UpdateFoo could be named “ChangeFoo” or

“ModifyFoo”; and so forth.

C# [DiscoverableType(DiscoverableTypeMode.ServerService)]

// or [EnableClientAccess] public class PocoSaveAdapterUsingConventions {

public PocoSaveAdapterUsingConventions() {

}

public void InsertFoo(Foo entity) {

// insert logic for any „Foo‟ entities

}

public void InsertBar(Bar entity) {

// insert logic for any „Bar‟ entities

}

public void UpdateFoo(Foo current, Foo original) {

// update logic for any “Foo‟ entities

}

public void UpdateBar(Bar current, Bar original) {

// update logic for any “Bar‟ entities

}

public void DeleteFoo(Foo entity) {

// update logic for any “Foo‟ entities

}

public void DeleteBar(Bar entity) {

// update logic for any “Bar‟ entities

}

}

Summary – Things to Remember When Using POCOs in Your DevForce

App Be sure to add the assembly that contains the POCOs as a top-level ProbeAssembly (i.e., not a in the

ProbeAssemblies section of an EdmKey element) in the app.config or web.config.

POCO classes must be included in both the server- and client-side assemblies. Put them in the server-side

project and link them into the client-side project.

Include a POCO ServiceProvider class server-side.

Include EntityManager extensions client-side.

You can use all forms of LINQ query against these objects.

If your POCO class has a primary key (designated with the [Key] attribute, instances of it will be stored in

the EntityManager cache, and can be updated, deleted, or inserted there.

If your POCO class has no key, instances can be retrieved but will not be placed in the EntityManager

cache.

Implement IHasEntityAspect to get EntityAspect property and associated functionality on your “almost

POCO” entity.

Page 330: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

330 | P a g e

Validation Through Verification

Validation Through Verification DevForce Verification

Getting Started Validation-Related Settings In the Object Mapper Generated Property Code Impact of Verifiers on the User Interface – A Caution

Now That You‟ve Been Initiated (and Before We Enter the Forest): A Quick Overview of the

Mechanics

Verification Types Overview Main Verification Classes Verifiers VerifierResult Triggers VerifierEngine PropertyValueVerifiers

Verification Deep Dive Verifiers Verifier Result Triggers VerifierEngine

Invoking Verification Instance Verification Trigger Verification: Preset and Postset Monitor Execution with the VerifierBatchInterceptor

Verification and WinForms User Interfaces UI Lockup Improving the User‟s Experience

“Validation” is the process of evaluating input and judging it valid or invalid. Such evaluation subjects the input to

a battery of “validation rules” that evaluate the input in the appropriate context. For example, if the user enters a

“committed delivery date” we might want to ensure that:

the committed delivery date is reasonable in the abstract, e.g., occurs in the future;

it is possible to deliver on that date given the availability of the desired products, and the currently

selected shipping method, and whether there is enough time to prepare the goods for shipping;

the order is “shippable”, e.g. the customer‟s credit has been verified, the address is legitimate, and the

total is within the limits authorized for this user.

Clearly such rules can be complex, involving not only the input value itself but also the state of the target object (the

order), facts about related objects (customer, shipper, product), and aspects of the environment during the validation

(time of day, the user‟s role).

Page 331: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

331 | P a g e

User input validation gets most of the attention but we need to validate programmatic inputs as well. That delivery

date could as easily be set by business logic or a web service request as it is by a wayward click on a calendar

control. The rules are the same for everyone, human and machine.

Validation is hard to do well especially as the application grows and validation criteria change. Common failings

include:

Missing and incorrect validity checks

Inconsistent checking

Failure to validate at the right times

Poor communication with end-users

Inadequate mechanisms for correcting mistakes.

Enterprise application developers are looking for a robust validation system that operates consistently and reliably

across a large application. Robust validation cuts both “vertically” and “horizontally”:

We validate “vertically” when we validate several times in multiple layers of the application. We want to

validate in the client UI layer so we can give immediate feedback to the user. We may need to validate

again when we save, even though the objects we save are no longer on screen. We may even need to

validate again on the server side to protect against misadventure coming from outside the relative safety of

the hosted environment.

We validate “horizontally” when we apply the same mechanisms uniformly across all modules of the

application. If the user can set the delivery date on any of several screens, the same rules ought to apply –

unless, of course, there is something special about a particular screen.

DevForce Verification

“Verification” is IdeaBlade‟s answer to the challenges of validation. “Verification” is a collection of interoperating

validation components that are both easy to use and capable of handling sophisticated scenarios. The developer can:

Write rules of any complexity. The developer can draw upon pre-defined rules (required value, range

check, field length) or write custom rules of any complexity, including rules that compare multiple

fields and span multiple objects.

Generate validity checking into business objects automatically via the DevForce “Object Mapper.

Validate any kind of object, not just objects that derive from base business classes.

Trigger validity checking at any time such as upon display, before save, or when setting properties. The

engine can fire “pre-set” to block critically errant data from entering the object or fire “post-set” to

accommodate temporarily invalid values. The UI can inspect the engine for rules of interest, fire them,

and adjust the display accordingly. It could color a text box, for example, or hide a portion of the form

until applicable criteria were met.

Display a localized message in the UI without special programming. The UI could display just the

“validation failed” message but it might also show warnings or “ok” messages and it might supplement

the message be re-directing the application focus to the offending object and property. Each rule returns

a rich, extensible object with all the information necessary for the developer to deliver a helpful

response.

Discover rules in the code or retrieve them at runtime from a central store. The engine automatically

discovers rules in the code and can acquire rules defined externally in configuration XML, a database,

or some other store of rules. The application can inspect, add, and remove rules at any time.

Leverage rules inheritance. Rules defined in base classes propagate to their derived classes where they

are “inherited” or overridden.

Page 332: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

332 | P a g e

Adjust validation behavior based on a custom validation context. The developer must have the

flexibility to convey custom information to the validation process to cope with the variety of situational

factors that arise in real applications.

Inspect and intervene as the engine validates. The application can monitor the engine‟s progress and

interrupt, modify, or terminate a validation run at any point.

“Verification” Versus “Validation”

The DevForce validation mechanism is called “Verification” and all of its components are named with some

variation on this word. We mean to try neither your patience nor your vocabulary. We would call our offering

“validation” if we could.

However, Microsoft uses the term “validation” throughout .NET. It appears in Windows Presentation Foundation

(WPF) and Windows Workflow Foundation (WWF) namespaces and in the Enterprise Library as well. Microsoft

also uses the following class names:

ValidationError, ValidationErrorCollection, ValidationManager, ValidationResult,

ValidationRule, ValidationStatus, ValidationType

IdeaBlade is integrating DevForce with Microsoft‟s WPF and WWF. You are likely doing the same. We will all

become confused if we cannot easily distinguish among the same or very similar names.

So “Verification” it is. We will continue to say “validation” when we speaking in general terms; we will use the

term “verification” (and its variants) when we refer specifically to the DevForce classes located in the

IdeaBlade.Verification namespace.

Whither WPF Validation?

We can‟t leave this digression without a parting comment about validation in Microsoft‟s Windows Presentation

Foundation.

WPF validation concentrates on presentation of validation results within a WPF user interface. This is a vital aspect

of any validation strategy. At present, most applications punish the user for the developer‟s own design failings. We

need better UIs and better means to guide users rather than humiliate them.

DevForce‟s “verification” concentrates on the validation process. It complements WPF by producing the rich

validation results necessary to deliver an effective user experience. We will address the integration of these

mechanisms in a separate document.

Getting Started

The easiest point of entry to DevForce verification is through the DevForce Object Mapper.

We‟ll assume that you are familiar with the Object Mapper and have an existing, working application with

its own business object model. See the “Hello DevForce” topic document in the DevForce Learning

Resources under “Introduction to DevForce” for a walk-through of the Object Mapper.

Validation-Related Settings In the Object Mapper

1. Launch the Object Mapper in Visual Studio.

2. Select an Entity Model node in the tree control and drop down the Verification Interceptors ComboBox

in the properties pane:

Page 333: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

333 | P a g e

3. Note the default setting of “Both”. This means that a check will be made for applicable verifiers before

and after the value of any property of any class in your model is set (which is to say, before and after a

proposed value is actually pushed into the business object).

However, this default setting setting can be overridden for any individual property using the ComboBox

in the “Verification Setter Mode” column of the property grid:

Page 334: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

334 | P a g e

4. Return to the Entity Model dialog by selecting the Entity Model node in the navigation pane. This time

note the CheckBox labled “Generate Validation Attributes” and the RadioButtons labeled “DevForce”

and “.NET”. The CheckBox determines whether properties generated into your object model will be

decorated with any sort of validation attribute; the RadioButtons determine whether the attribute used

will be one of those defined in the .NET framework, or DevForce‟s validation attribute

Generated Property Code

Here we‟ll show you the three basic styles of validation-related attribute decoration that you can select in the Object

Mapper.

DevForce Validation Attributes

Here is the FirstName property of an Employee object as generated with the settings shown above:

Page 335: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

335 | P a g e

C#

VB

IbVal is an alias for the IdeaBlade.Validation namespace, defined at the top of the code file. The

IbVal.ValidateProperty attribute tells DevForce to check for verifiers when this property gets set, and run any

whose settings define them as appropriate. The IbVal.StringLengthVerifier sets a maximum length on the (text)

value, and its IsRequired argument declares the property non-nullable.

The IbCore.MaxTextLength attribute is used only with WinForm user interfaces, by the DevForce

BindingManagers. It extends a legacy established in DevForce Classic.

.NET Validation Attributes

Page 336: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

336 | P a g e

Here is the generated code that results from changing the RadioButton selection to “.NET”:

C#

VB

Note that the non-nullability (i.e., Required) and string length constraints are specified a bit differently.

DevForce can make use of either style of validation attribute. Its own versions provide richer capabilities than

the .NET counterparts, but if you need your code to use the .NET attributes for reasons of your own, DevForce

cooperates.

Neither DevForce Nor .NET Validation Attributes

Here is the generated code we get after unchecking the “Generate Validation Attributes” CheckBox:

Page 337: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

337 | P a g e

C#

VB

DevForce still indicates that validation should be run against the property, so that if you define any custom verifiers

using other mechanisms (to be discussed shortly) they will get exercised; but there is now no indication from the

property attributes that the property either requires a value, or is limited to any particular length.

Page 338: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

338 | P a g e

Impact of Verifiers on the User Interface – A Caution

As you‟ve just seen, DevForce automatically generates verifiers to enforce constraints implicit in column definitions

of the backing datastore. In our example, we saw that, because the FirstName column of the Employee table behind

the Employee object class is defined as non-nullable and 30 characters wide, DevForce generates a verifier to

impose corresponding constraints on the Employee.FirstName property.

Suppose our application displays Employee information on a form, as shown below. Suppose our end user deletes

the contents of the Employee “First Name” textbox to start. She attempts to move out of the field … but can‟t.

Instead, she sees this:

She can‟t change any value on the form. She can‟t move to a different Employee. She can‟t even close the form! The

user must enter something in the “First Name” textbox. What happened?

DevForce generated a validation rule, known in DevForce as a Verifier.

It put code in our business object that caused validation to be invoked when the user tried to set the

value.

The validation failed when the user cleared the “First Name” control, since the value for that property is

Required.

The form responded by locking the text box and displaying an “error bullet” with an informative

message.

This behavior can be addressed in various ways – mainly quite simple and straightforward. We address the topic

further as it pertains to WinForms in the section “Verification and WinForms User Interfaces” later in this

document. But that‟s all another discussion, so on with our discussion of the basics!

Page 339: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

339 | P a g e

Now That You‟ve Been Initiated (and Before We Enter the Forest):

A Quick Overview of the Mechanics

What you‟ve seen thus far in this document are verifiers defined with property attributes. You get these for free with

DevForce. Simply by flipping a switch in the Object Mapper (actually, it‟s set ON by default!) you get all of the

basic constraints defined on column values by the backing database carried through to your business classes in the

code that the Object Mapper generates.

This, however, is by no means the extent of the validation capabilities that DevForce provides. We‟ll describe the

Verification system in detail later in this document, but first, so you don‟t lose the forest for the trees, let‟s take a

moment for a high-level overview of how the system works, what you can do with it, and how you do it.

Creating a Verifiers Collection for a Type

When one of your business types is first instantiated during an application session, a VerifierEngine is invoked to

discover all verifiers applicable to that type. Some of these verifiers are defined using property attributes, as you‟ve

seen; but verifiers may also be defined in .NET code -- in very flexible and powerful ways! – and even in XML in

the app.config file. The VerifierEngine discovers all of the verifiers, however encoded, and creates an in-memory

collection of them. They are then available for it to call upon whenever needed.

Execution Modes for a Verifier

Each of the verifiers has its own properties which tell the VerifierEngine when it should be run. For example, you

can define a verifier so that it runs before a proposed new property value is pushed into the business object (the

default for the generated verifiers); or only after; or both. You also want most verifiers to run whenever an entire

instance of a type is being validated. To specify these things, you specify the ExecutionModes for a verifier. Your

selections are:

OnPostSetTriggers

OnPreSetTriggers

Instance

InstanceAndOnPostSetTriggers

InstanceAndOnPreSetTriggers

All

Disable

PreSet triggers run before the proposed new value is pushed into the business object; PostSet triggers run after.

Although it is desirable never to allow invalid values into a business object in the first place (hence the default

ExecutionMode of OnPreSetTriggers for generated verifiers), sometimes you can only know that a value is invalid

by comparing it to the value of some other property of the same or another object. In that event, you must allow the

new value to be pushed into the business object, because the fix for an invalid value may actually be to change the

value of something else.

Defining Triggers for a Verifier

Consider, for example, a constraint that specifies that the HireDate for an Employee must be later than the

Employee‟s BirthDate. That seems a pretty reasonable requirement, but suppose when the user enters a HireDate of

today, this rule is found to have been violated. But the Employee really was hired today, and the problem is that the

BirthDate previously entered for the Employee was in error, specifying the year 2015 when it should have said

1985. If we prevent the new HireDate value from being entered into that property, we‟ll subject the user to a lot of

unnecessary work. She‟ll have to clear the new HireDate, even though it is entirely correct, and then go fix the

BirthDate value, and then come back and re-enter the new HireDate value, even though she got it right the first time.

Page 340: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

340 | P a g e

Users don‟t have a lot of tolerance for this sort of tom-foolery! It‟s confusing, or irritating, or often both, in

succession.

A verifier that applies to BirthDate doesn‟t have to be triggered by a change to that property. You can, instead – or

additionally – specify that you want it called when the value of HireDate is changed. You can even set it to be

triggered by a change to a property of some other type; and not just any instance of that type, but one that happens to

be specifically related. For example, suppose you have a rule that says that the OrderDate on an Order taken by

Employee X must be greater than or equal to that Employee‟s HireDate. You could define this rule (as a verifier) on

the Employee type, but specify that it should be triggered not only by a change to Employee.HireDate, but equally

by a change to the OrderDate property of any Order written by that Employee!

Pre-Defined Verifiers

DevForce ships with a number of pre-defined verifiers that can be subclassed in flexible ways: examples include the

DateTimeRangeVerifier, the PhoneNumberVerifier, the NamedRegexPatternVerifier, the StringLengthVerifier, the

RequiredValueVerifier, and a number of others. In addition, it defines a generic DelegateVerifier<T> that provides

unlimited verification capabilities: you can use it to define any sort of rule you need or can imagine.

Fast Track to Verification for the Prudently Impatient

You‟ll find examples of all of these types of verifiers in the sample code solutions included in the Validation area of

the Learning Resources. If you want to see the code right away, feel free to take a side trip now and jump in. There‟s

a decent chance you‟ll be able to figure out how to do most of what you need to do from what you‟ve already read

and from the samples there. If not, come back and read some more.

For our purposes here, it‟s time to dig into the details of the Verification facilities.

Verification Types Overview

Most Verification classes are defined under the IdeaBlade.Validation namespace and deployed in a single

DevForce class library, IdeaBlade.Validation.dll.83

This section is a guide to the key Verification constructs

in that library.

Main Verification Classes

83 There are some legacy classes having to do with WinForm support that are defined in IdeaBlade.Verification.dll. You will

not need these if doing WPF or Silverlight applications.

Page 341: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

341 | P a g e

These are the main classes, the classes at the heart of the DevForce Verification paradigm.

Type Description

VerifierEngine A VerifierEngine maintains a list of Verifier instances in its

VerifierCollection and executes them at the appropriate times,

accumulating their VerifierResults in its VerifierResultCollection.

A Verifier should not run independently. Rather it should be evaluated

by a VerifierEngine and the caller should reap the results from the

engine‟s Execute method when it finishes.

Verifier

VerifierCollection

A Verifier validates the state of an object.

This is the abstract base class for a family of verifiers described below.

VerifierResult

VerifierResultCollection

Verifier execution produces a VerifierResult object. This object, in

addition to signaling validation success or failure, contains detailed

information about the outcome and the context of the verifier‟s

execution.

TriggerItem A TriggerItem identifies something like an “event”.

When the VerifierEngine executes in the context of this “event”, it

evaluates all verifiers attached to that TriggerItem.

A property is the most commonly encountered TriggerItem. The setting

of that property is the associated “event”. The engine looks for all

verifiers that are attached to that property “in the right way” and

executes them.

TriggerLink Triggered verifiers have one or more TriggerLinks, each of them

connecting the verified object to a TriggerItem.

The TriggerLink specifies both the TriggerItem and a “path” back to

the verified object.

In the case of a simple property it is the very short path from the

property to the instance as in the path from Employee.FirstName to

Employee.

Developers can write complex paths that “navigate” from a triggering

“event” on an object that is far removed from the object being validated.

For example, changing a customer‟s credit limit property (the trigger)

could stimulate verification of all outstanding orders (the verified

objects) related to (the path to) that customer.

The Verification types are all interrelated, but we separate them by category for explanatory purposes.

Verifiers The Verifier class and its supporting types.

VerifierResult VerifierResult and related types.

Triggers TriggerItem, TriggerLink, and related types.

VerifierEngine The VerifierEngine and its supporting types

PropertyValueVerifiers Describes the pre-defined verifiers for the most commonly encountered

validation cases.

Verifiers

A Verifier validates the state of an object. A Verifier can only run after it is instantiated by a VerifierEngine.

Page 342: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

342 | P a g e

The Verifier class is the abstract base class for derived verifiers that are attuned to specific validation tasks; we

cover these derived classes in a separate section below.

The following table highlights significant members of the Verifier class.

Class Member Description

AddTrigger, AddTriggers

Adds a TriggerLink to the verifier. The verifier subsequently responds

to “events” associated with the TriggerItem in that TriggerLink.

ApplicableType The verifier validates objects of this type

DefaultSortOrder Static property that reveals the SortOrder given to new verifiers by

default.

Description The description of the verifier as displayed to the user.

ExecutionModes The situations in which the verifier should run. The value combines

flags from the VerifierExecutionModes enumeration.

GetDisplayName Returns a name for the member of a type as it should be displayed to the

user. “First Name” might be the display name for the FirstName

property of an Employee. The method is often used to construct the

Description from a message template.

InitializationOrder The integer position of this Verifier in its engine‟s list.

IsApplicable Runs the method that indicates if this Verifier applies to a given instance

when run in a particular context. Returns a

VerifierApplicabilityresult.

OnErrorMode Set to one of the VerifierOnErrorMode enumerations (Stop, Continue)

that tells the engine whether it should stop or continue verifying if this

verifier produces an errant VerifierResult. Continue is the default.

RemoveTrigger, RemoveTriggers

Removes a TriggerLink from the verifier. The verifier no longer

responds to “events” associated with the TriggerItem in that

TriggerLink.

SortValue The engine executes verifiers in sorted order. The engine sorts verifiers

first by this SortValue and, when those are the same, by the order in

which the verifiers were added to the engine (InitializationOrder).

Thus, the developer can influence verifier processing order by setting

this SortValue.

TriggerLinks Returns the TriggerLinks attached to this verifier.

VerifierArgs Configuration data for the Verifier. Every Verifier is created with a

VerifierArgs instance, either explicitly or implicitly.

VerifierEngine The engine to which the verifier is attached.

Page 343: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

343 | P a g e

Class Member Description

Verify For Internal Use. This method implements the verifier‟s core

validation test and can be overridden by a derived class.

The method is public so that a VerifierEngine can call it at the

appropriate time. Developers cannot call it directly – it will throw an

exception.

The following is list of types that are closely related to Verifier. The list is (mostly) alphabetical to make it easier

to locate a type and for lack of more compelling organizational principle.

Type Description

ApplicabilityConstraint(Of T) Delegate that determines if a Verifier applies to a particular object

given the current TriggerContext and VerifierContext. It returns a

VerifierApplicability object. T is the type of the verified object.

The developer can invoke this constraint directly by calling

Verifier.IsApplicable.

DelegateVerifier(Of T) A harness for a custom verifier that validates an object of type T. The

developer can build almost any kind of verifier with an instance of this

class.

The developer writes the validation test inside a VerifierCondition

delegate (hence the name) and includes a reference to the delegate

method in the DelegateVerifier constructor. This verifier can be

configured with triggers in the same way as all other verifiers.

DelegatePropertyValueVerifier (Of T)

A harness for a custom property verifier that validates an object of

type T. Used to build a verifier triggered by a single property with the

purpose of evaluating the proposed or actual value of that property.

The developer implements the validation test inside a method that

conforms to the ValueVerifierCondition delegate and passes a

reference to the method in the DelegatePropertyValueVerifier

constructor.

The verifier behaves like any of the predefined PropertyValueVerifier

classes described below.

PropertyValueVerifierAttribute Each of the PropertyValueVerifiers can be specified declaratively by

decorating a property with the corresponding

PropertyValueVerifierAttribute.

TriggerContext An object passed to a Verifier when it is triggered by a TriggerItem.

The object provides the Verifier with information about what triggered

it.

Trigger classes are covered separately below.

ValueVerifierCondition(Of T) Delegate that determines if a value passes its verifier test. It returns a

VerifierResult. T is the type of the verified object.

The developer can supply such a delegate as an argument to the

constructor of a DelegatePropertyValueVerifier.

Verifier Abstract base class for a family of Verifiers. A Verifier validates the

state of an object and returns a VerifierResult containing detailed

information about the validation outcome and the context of the

verifier‟s execution.

Page 344: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

344 | P a g e

Type Description

Verifier(Of T) Strongly typed abstract subclass of Verifier where T is the type of the

verified object.

VerifierApplicability The Verifier determines if it applies to an object it is validating based

on an ApplicabilityConstraint. That constraint method returns an

object of this type which contains both a VerifierApplicabilityCode

and an optional message.

VerifierApplicabilityCode An enumeration of result codes emerging from evaluation of an

ApplicabilityConstraint.

VerifierArgs These args carry configuration data for a Verifier. Every Verifier is

created with a VerifierArgs instance, either explicitly or implicitly;

every Verifier retains a reference to that instance.

This is also the base class for a family of VerifierArgs classes, each

strongly typed to fit closely with it corresponding Verifier class. The

ListVerifier has its ListVerifierArgs for example.

VerifierAttribute Abstract base class for a family of Attribute classes that enable

declaration of a Verifier by decoration with an attribute. For example,

we can declare that the FirstName property has a StringLengthVerifier

by adorning it with the StringLengthVerifierAttribute.

VerifierCollection A collection of Verifier instances. The collection implements many of

the features of List<Verifier> and, most importantly, many Find

overloads to facilitate extraction of Verifier subset collections.

VerifierCondition(Of T) Delegate that implements a validation test on an object of type T. This is

the beating heart of the developer‟s custom DelegateVerifier.

VerifierContext The VerifierEngine executes a Verifier in a particular context and

makes this context available to the Verifier as it executes.

VerifierOnErrorMode The enumeration (Stop, Continue) that tells the engine whether it should

stop or continue verifying if this verifier produces an errant

VerifierResult.

The developer can set the Verifier.OnErrorMode to a value from this

enumeration.

VerifierException The exception thrown when the Verifier itself fails to execute properly,

i.e. when the Verifier throws an exception; that exception is included in

the InnerException.

Not to be confused with the VerifierResultException.

VerifierExecutionModes A flag enumeration (Disabled, Instance, OnPostsetTriggers,

OnPresetTriggers) that describes the situations in which a Verifier can

run.

The VerifierEngine, while executing in one of these situations, runs the

verifiers that have a matching ExecutionModes flag.

The developer can set a Verifier to run in multiple situations by setting

its ExecutionModes to a combination of these flags constructed by

“or”ing them together. The VerifierExecutionModes enumeration

exposes several of the most common combinations (e.g. All which

translates to Instance | OnPostsetTriggers | OnPresetTriggers ).

VerifierResult

Page 345: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

345 | P a g e

Verifier execution produces a VerifierResult object84

. This object, in addition to signaling validation success

or failure, contains detailed information about the outcome and the context of the verifier‟s execution.

84 We may say casually that a Verifier returns a VerifierResult but this is not strictly correct and might mislead the

developer into improper use of verifiers. While it is true that the Verifier.Verify method returns a VerifierResult,

that method executes only one part of the Verifier‟s validation logic and should not be called by developer code. The

Verifier should be executed by a VerifierEngine which stores the VerifierResult in its own results collection. The

developer retrieves those results from the engine.

Page 346: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

346 | P a g e

The following table highlights significant members of the VerifierResult class.

Class Member Description

Description Description of this result. The text is usually fashioned in a form suitable

for display when the result is “not Ok”.

IsOk True if the result has one of the “Ok” VerifierResultCodes.

ResultCode The VerifierResultCode for this result.

TargetInstance The object that was verified.

TriggerContext The TriggerContext in which the verifier was executed.

Verifier The Verifier whose execution produced this result.

VerifierContext The VerfifierContext in which the verifier was executed.

The following are the important types that are most closely related to VerifierResult.

Type Description

VerifierResultCode Enumeration summarizing the result of verification in a single value.

While there are several codes, each is a flavor of a binary outcome:

success (Ok) or failure (Error).

The codes at this writing are: Error, ErrorInsufficientData, Ok,

OkNotApplicable, OkWarning.

VerifierResultCollection The VerifierEngine accumulates a collection of VerifierResult

instances which it returns as a VerifierResultCollection from its

Execute method. The collection implements most of the features of

Collection<VerifierResult> and, importantly, many Find overloads to

facilitate extraction of VerifierResult subset collections.

VerifierResultException The caller of the VerifierEngine may want to throw an exception if it

detects an errant VerifierResult. The VerifierResultException is a

strongly-typed exception for this purpose; it can report the initial errant

VerifierResult as well as a VerifierResultCollection of other results

that may be useful to a handler of the exception.

The Entity.BeforeSetValue and Entity.AfterSetValue methods are

examples of VerifierEngine callers that throw

VerificationResultExceptions.

The Verifier and the VerifierEngine do not themselves throw this

exception; they merely report errors by providing VerifierResults.

Do not confuse the VerificationResultException with the

VerificationException. A Verifier or VerifierEngine will throw a

VerificationException when the verifier fails to execute properly.

Improper verifier execution is not the same as an invalid object

condition.

Page 347: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

347 | P a g e

Type Description

VerifiersErrorsResource.resx A resource file of predefined error message templates.

This resource file contains the message templates for constructing

VerifierResult descriptions.

The developer can substitute a different .NET ResourceManager that

governs a wider set of message templates and resources files for

different locales; the developer‟s main resource file must contain

definitions for all of the message names defined in the

VerifiersErrorsResource.resx.

The DevForce distribution includes this resource file as a starting place

for the developer‟s own resource file (and satellite translation files).

Triggers

Evaluation of a Verifier may be triggered by one or more “events”.

“Events” is in quotes because the mechanism, while it feels like an event, does not use the .NET event. The

exact mechanism is introduced here and covered more extensively elsewhere in this document.

Setting a property is likely the most commonly encountered trigger. Setting Employee.FirstName, for example,

could trigger evaluation of a Verifier that checked if the FirstName string value is present and not longer than

thirty characters.

The Verifier that checks the FirstName string length can be evaluated independently of any trigger. It could be

evaluated during validation of an Employee instance85

. But we often want to verify the value the moment the user

enters the text. Accordingly, the developer attaches a trigger to that Verifier – a trigger bound to the

Employee.FirstName property.

TriggerItem

DevForce represents the triggering Employee.FirstName property as a TriggerItem. A TriggerItem is little

more than a .NET Type and the name of some member on that type. If a TriggerItem represents a property, the

member name is the property name.

TriggerLink

It will not always be enough just to know the TriggerItem. We may have to find our way back from the trigger to

the object being verified.

This is easy when the TriggerItem refers to a property of the object being verified. If the trigger is

Employee.FirstName and the Verifier targets the Employee object, it is obvious that “the way back” from the

property to Employee involves no effort at all: the triggering object and the verified object are the same.

On the other hand, we may want to evaluate the verifier when a value changes on some different object. For

example, we may want to verify that an Order‟s total price is still valid if the price of any of its OrderDetail items

goes up. The OrderDetail is not the same object as the Order we need to verify.

The TriggerLink provides the path from the OrderDetail whose price changed to its parent Order which must

be verified.

85 Evaluation in this situation is called “Instance Verification”.

Page 348: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

348 | P a g e

The TriggerLink holds both the end point (the TriggerItem for OrderDetail.Price) and the method to

navigate from the trigger object (OrderDetail) to the object to verify (Order). This method is called the

TriggerTargetNavigator.

We‟ll cover all of this in greater depth later; for now we look at the classes and other types involved in triggering

execution of a Verifier.

Type Description

TriggerContext An object passed to a Verifier when it is triggered by a TriggerItem.

The object provides the Verifier with information about what triggered

it.

TriggerItem A TriggerItem identifies something like an “event”. A

A TriggerItem is defined by the Type of the triggering object and the

name of a member on that type that does the triggering.

TriggerLink A TriggerLink specifies both the TriggerItem and a path back to the

verified object.

The path is implemented by a TriggerTargetNavigator method. That

method is null when the triggering object and the object to be verified

are the same as they are when we trigger a verifier for

Employee.FirstName when the user sets that property.

The navigator could be a.NET property path (e.g. a

PropertyDescriptor). It could also be a custom method capable of

bridging the two object types; see TriggerTargetNavigator.

TriggerTargetNavigator Delegate for “navigating” from a TriggerItem to the object being

verified. See TriggerLink.

TriggerTiming An enumeration available within the TriggerContext. It indicates when

a verifier was “triggered”. There are two choices: Preset and Postset.

Properties are the most common triggers so a TriggerTiming typically

indicates whether the verifier was evaluated before or after the property

was set.

VerifierEngine

Verifiers do not execute themselves86. They are executed by a VerifierEngine instance. Each engine maintains a

list of Verifier instances and evaluates them at the “appropriate” times based on a variety of factors that include

(but are not limited to) properties of the verifiers themselves.

86 You should not call the Verifier.Verify method directly even though it is public. That method performs some – but not

all ! – of the validation work and will throw an exception if called outside a VerifierEngine.

Page 349: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

349 | P a g e

The details of the engine are covered elsewhere in this chapter. Here are the types most relevant to understanding it.

Type Description

VerifierCollection The engine maintains a collection of Verifier instances, accessible via

one of the GetVerifier method overloads.

The collection implements most of the features of List<Verifier> and,

importantly, many Find overloads to facilitate extraction of Verifier

subset collections.

VerifierContext The VerifierEngine executes a Verifier in a particular context.

The engine creates an instance of a VerifierContext before every

validation run (a “batch”) and makes it available to the verifiers in that

run.

Each verifier can both see and modify the context. The developer can

activate a VerifierBatchInterceptor delegate method that can see and

modify the context.

The context includes a great deal of useful information including a

reference to the engine itself, the BatchId of the engine‟s current

validation run, the VerifierResultCollection of results accumulated so

far, the currently executing Verifier, and a CustomContext object

supplied by the developer.

VerifierEngineCreatedEventArgs EventArgs provided to a VerifierEngine.VerifierEngineCreated event

handler. The VerifierEngine class raises this static event after creating

a new VerifierEngine instance. The developer can attach a handler to

consistently configure every new instance.

VerifierEngine. PropertyNameTranslator

Delegate method that takes a type and a string (presumed to be the

property name) and returns the string that will be injected into the

message produced by the verifier.

A verifier description or message should appear in the user‟s preferred

language.

The message templates can be localized but they often have a

placeholder for the property name. The “{0}” in the message “{0} is

required” will be filled by a property name at runtime. This name

should be localized as well.

The VerifierEngine.PropertyNameToDisplayNameTranslator property

takes such a delegate.

VerifierEngine. VerifierBatchInterceptor

Delegate method called by the VerifierEngine after every verifier

evaluation in a batch and once more at the end of the batch.

VerifiersErrorsResource A resource file of predefined error message templates.

VerifierException The exception thrown when the Verifier itself fails to execute properly

within the engine, i.e. when the Verifier throws an exception; that

exception is included in the VerifierException.InnerException.

Not to be confused with the VerifierResultException.

VerifierExecutionModes A flag enumeration (Disabled, Instance, OnPostsetTriggers,

OnPresetTriggers) that describes the situations in which a Verifier can

run.

The VerifierEngine, while executing in one of these situations, runs the

verifiers that have a matching ExecutionModes flag.

Page 350: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

350 | P a g e

Type Description

VerifierOnErrorMode The developer can set the Verifier.OnErrorMode to a value from this

enumeration (Stop, Continue). The value tells the VerifierEngine

whether it should continue (the default) or stop verifying if this verifier

reports that its validation failed.

VerifierProviderAttribute

VerifierResult The result of executing a Verifier. It contains detailed information

about the outcome and the context of the verifier‟s execution

VerifierResultCollection The VerifierEngine accumulates a collection of VerifierResult

instances which it returns as a VerifierResultCollection from its

Execute method. The collection implements most of the features of

Collection<VerifierResult> and, importantly, many Find overloads to

facilitate extraction of VerifierResult subset collections.

VerifiersChangedEventArgs Args of the VerifierEngine.VerifiersChanged event, raised when a

verifier is added to or removed from the engine or a trigger is added to

or removed from a verifier already held by the engine.

VerifiersChangedType Enumeration of the types of changes reported in VerifiersChangedEventArgs

PropertyValueVerifiers

The class diagram for Verifier and its derived classes as of this writing looks like this:

Most of the verifier classes are PropertyValueVerifiers. A PropertyValueVerifier tests a property value.

Technically, it is a Verifier attached to single TriggerItem which is a property on the object being validated.

Page 351: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

351 | P a g e

The value to test may be the proposed property value (prior to the property set) or the current value (after the

property was set).

Many application validations are property validations and most of these resolve into some variation of just a few

kinds of verifier: required, range or length, and membership in a list.

Attribute Classes

Verifiers can be prescribed programmatically and added to the VerifierEngine at runtime. It is sometimes

convenient to prescribe them programmatically by adorning properties with attributes. The DevForce includes a

number of PropertyValueVerifierAttribute classes to facilitate this approach.

The DevForce Verification library covers many of these verifiers and attributes; of course you can easily extend

them or write your own.

Null property values

We have to check for null before we can test a property value. In many cases, null values are not permitted. Rather

than oblige the developer to specify both a RequiredValueVerifier and the verifier of interest, all

PropertyValueVerifiers include an IsRequired parameter; the base, abstract PropertyValueVerifier

evaluates IsRequired before handing the value on to the derived verifier classes.

The outcome of the test is often arbitrary in the face of a null value; is a null BirthDate before or after the

minimum date in a range check? You should be sure you know how the verifier handles nulls.

The following table highlights significant members that are specific to the PropertyValueVerifier class.

Type Description

DisplayName The displayable name of this verifier; this is typically the display name

for the property it verifies. See also the Verifier.GetDisplayName

method.

GetPropertyValue Returns the value of this property as it currently is in the object being

verified. This value could be compared to the proposed value if the

verifier is executing in a “preset” context.

IsRequired Returns true if a property value is required (if it cannot be null).

PropertyDescriptor The .NET PropertyDescriptor for the property it verifies.

TypeVerifierArgs Returns the strongly type PropertyVerifierArgs that configure this

verifier.

Page 352: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

352 | P a g e

The following are types closely related to this class and its derived classes.

Type Description

DelegatePropertyValueVerifier (Of T)

The foundation of a custom property verifier. The developer implements

the validation test inside a ValueVerifierCondition delegate of his own

devising. It behaves otherwise like any of the predefined property

verifiers.

NamedRegexPattern A Regex expression for use with the RegexVerifier. You can use one of

the pre-named static patterns or create your own “named” Regex

pattern.

PropertyValueVerifier Verifiers that apply to a single property of an object. The DevForce pre-

defined PropertyValueVerifiers, as of this writing, are:

DateTimeRangeVerifier

DecimalRangeVerifier

DelegatePropertyValueVerifier(Of T)

DoubleRangeVerifier

Int32RangeVerifier

Int64RangeVerifier

ListVerifier

RangeVerifier

RegexVerifier

RequiredValueVerifier

StringLengthVerifier

PropertyValueVerifierAttribute Each of the PropertyValueVerifiers can be specified declaratively by

decorating a property with the corresponding

PropertyValueVerifierAttribute.

DateTimeRangeVerifierAttribute

DecimalRangeVerifierAttribute

DelegatePropertyValueVerifierAttribute

DoubleRangeVerifierAttribute

Int32RangeVerifierAttribute

Int64RangeVerifierAttribute

RangeVerifierAttribute

RegexVerifierAttribute

RequiredValueVerifierAttribute

StringLengthVerifierAttribute

RangeVerifier(Of T) A generic range Verifier where T is the type of value tested (not the

type of the verified object).

A range verifier accepts arguments specifying minimum and maximum

(either optional) and whether the range includes or excludes either end

point.

ValueVerifierCondition(Of T) Delegate that determines if a value passes its verifier test. It returns a

VerifierResult. T is the type of the verified object.

The developer can supply such a delegate as an argument to the

constructor of a DelegatePropertyValueVerifier.

Page 353: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

353 | P a g e

Adding Attributed Verifiers to Generated Properties

Previously you saw an example of the code generated by the Object Mapper for a string-valued property, FirstName.

The property definition was decorated with a StringLengthVerifier :

C#

[StringLengthVerifier(MaxValue=30, IsRequired=true)]

public String FirstName {

...

}

VB

<StringLengthVerifier(MaxValue:=30, IsRequired:=True)> _

Public ReadOnly Property FirstName() As String

...

End Property

To add an attributed verifier to a custom property defined in your developer partial class, you would simply add the

appropriate attribute – such as the StringLengthVerifier attribute shown above – to the property definition.

Clearly you can‟t do the same for properties defined in the designer code file generated by the DevForce Object

Mapper. That file, and the code in it, “belongs” to the Object Mapper, which reserves the write to overwrite it

whenever ordered to do so.

Nevertheless, you can still apply your own attributed verifiers to generated properties. You do this by means of a

“buddy” class that partners with your business class and contributes additional metadata to it. In the example below,

we‟ve added such a buddy class to the developer partial class file for the Customer type, Customer.cs. In the buddy

class, we‟ve decorated the static property CompanyName with the StringLengthVerifier, assigning our own

MaxValue, which is more restrictive than the one generated for CompanyName in the designer code file.

Page 354: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

354 | P a g e

C#

...

using IbVal = IdeaBlade.Validation;

using DataAnnot = System.ComponentModel.DataAnnotations;

namespace DomainModel {

[DataAnnot.MetadataType(typeof(CustomerMetadata))]

public partial class Customer : IdeaBlade.EntityModel.Entity {

...

}

/// <summary>

/// The buddy class for Customer

/// </summary>

public class CustomerMetadata {

/// <summary>

/// Override CompanyName to make it required.

/// </summary>

[IbVal.StringLengthVerifier(MaxValue = 10, IsRequired = true)]

public static string CompanyName;

}

}

VB

...

Imports IbVal = IdeaBlade.Validation

Imports DataAnnot = System.ComponentModel.DataAnnotations

Namespace DomainModel

<DataAnnot.MetadataType(GetType(CustomerMetadata))> _

Partial Public Class Customer

Inherits IdeaBlade.EntityModel.Entity

...

End Class

''' <summary>

''' The buddy class for Customer

''' </summary>

Public Class CustomerMetadata

''' <summary>

''' Override CompanyName to make it required.

''' </summary>

<IbVal.StringLengthVerifier(MaxValue := 10, IsRequired := True)> _

Public Shared CompanyName As String

End Class

End Namespace

Important!! Note, in order that the Verification engine should be aware of the Customer type‟s buddy class, that we

have decorated the Customer class with the MetadataType attribute from the

System.ComponentModel.DataAnnotations namespace. Don‟t forget to do that: otherwise the verifiers defined in

your buddy class will not be enforced!

Page 355: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

355 | P a g e

Verification Deep Dive

Now that we have toured the Verification types we are ready to look more closely at the major types.

Verifiers

We use an instance of the DevForce abstract Verifier class to implement a validation rule.

Verifier = Validation Rule

A verifier‟s primary task is to render judgment on the validity of an object. It isn‟t suppose to change the object, just

evaluate it and pronounce the object valid or invalid.

That‟s a big job – too big for any one verifier instance87

. So we create lots of verifiers each of which limits itself to

evaluating one aspect of an object such as the string length of a single property. Each verifier produces a

VerifierResult object which, at its most basic, (a) indicates success (Ok) or failure (Error) and (b) provides a

message (the VerifierResult.Description) for display to a user. An object is “valid” if the accumulated

results of individual verifiers are all “ok”.

The DevForce Verification library contains several predefined Verifier subclasses88

as well as several higher

level abstract classes that allow developers to construct their own verifiers.

Verifiers don‟t execute on their own. They have to be evaluated by a VerifierEngine which means we have to tell the

engine about them by registering configured instances of some verifier class with the engine.

While we can register verifier instances programmatically, it is often more convenient to let the VerifierEngine

discover them – a process we‟ll get to when we consider the engine in detail. For now we‟ll talk about registration as

if we always took an active hand in it.

Each verifier has an ApplicableType which is the type of object that the verifier can verify. Verifiers with an

ApplicableType of a .NET base type are presumed to be applicable to all subclasses of that base type. The

VerifierEngine ensures that verifiers registered for a base class are propagated automatically the verifier

collection of all derived types.

Imagine that you had an abstract class called Produce and a bunch of subclasses – Carrot, Apple, Potato. When

you attach a verifier to Produce.Name, that same verifier applies to Carrot.Name, Apple.Name, and

Potato.Name89

.

Verifier Execution

A verifier cannot be executed until it has been added to a VerifierEngine. An individual verifier instance can be

attached to only one VerifierEngine at a time.

Verifiers are executed in the order that they were added to the VerifierEngine. It is possible to modify the order

by setting the SortValue property on each verifier.

A VerifierEngine runs in one of three “Execution Modes” at a given time. How we call it determines the mode.

Instance Verification

87 While it is possible to write a single super verifier that does it all, it would be unwise to do so.

88 See the class diagram above.

89 It is the same verifier even if Potato.Name overrides Produce.Name. The developer can remove or replace the propagated

verifier for Potato.Name by manipulating the Potato verifiers after they have been built. Carrot.Name and Apple.Name

will be unaffected.

Page 356: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

356 | P a g e

Preset Trigger Verification

Postset Trigger Verification

We cover these modes in detail in the “Invoking Verification” section. The point to note here is that the engine will

only evaluate the verifiers that are configured to run in a compatible execution mode. Thus, if the engine is running

in “Preset Trigger” mode and the verifier‟s ExecutionModes = InstanceAndOnPostsetTriggers, the verifier

will not be evaluated; it will be evaluated when the engine runs in either instance or postset trigger mode.

When a VerifierEngine evaluates a verifier it calls two verifier methods: IsApplicable and Verify.

C#

public virtual VerifierApplicability IsApplicable( Object pItemToVerify, TriggerContext pTriggerContext, VerifierContext pVerifierContext); public abstract VerifierResult Verify( Object pItemToVerify, TriggerContext pTriggerContext, VerifierContext pVerifierContext);

Visual Basic

Public Overridable Function IsApplicable( _ ByVal pItemToVerify As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) _ As VerifierApplicability Public MustOverride Function Verify(ByVal pItemToVerify As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) _ As VerifierResult

Observe that engine calls both methods with the same inputs

Parameter Description

pItemToVerify The object instance to verify. Its type will be the same as or a descendent

of the ApplicableType of the verifier.

pTriggerContext A TriggerContext object that describes how the verifier was triggered –

a topic covered elsewhere in this chapter.

Note that this value is null (Nothing in VB) when the verifier was not

triggered (i.e., during “instance verification”).

pVerifierContext

IsApplicable

The execution cost of some verifiers may be high. We don‟t want to pay that cost if the verifier does not apply in the

present circumstances. The developer can specify an IsApplicable method to short-circuit unnecessary verifier

evaluation. For example, most validations are irrelevant if the object is marked for delete. We might test for that in

our IsApplicable method.

The VerifierEngine calls the verifier‟s IsApplicable method first. The IsApplicable method returns a

VerifierApplicability object with a VerifierApplicabilityCode. If the code is Yes the engine continues

evaluating the verifier. If the code is anything else, the engine stops evaluating, prepares a VerifierResult for

this verifier, and moves on to the next verifier.

Page 357: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

357 | P a g e

The VerifierResultCode of the prepared VerifierResult will be an “ok” code

(VerifierResultCode.OkNotApplicable) if the VerifierApplicabilityCode is No. It will be an “error”

code (VerifierResultCode.ErrorInsufficientData) if the VerifierApplicabilityCode is

InsufficientData.

An applicability test is rarely needed. Accordingly, the base IsApplicable implementation in the abstract

Verifier class simply returns VerifierApplicability.Yes.

VerifierContext

The VerifierEngine provides both the IsApplicable and the Verify methods with a VerifierContext

defined as follows:

This context gives each Verifier information about its calling and executing environment including the engine‟s

progress during this particular execution. The context values change over the course of the verification. The

VerifierEngine will change them. Each Verifier can change them too.

Initializing the VerifierContext

The initial VerifierContext is either a context created by the engine or a context provided by the code that puts

the engine to work.

Such code calls one of the engine‟s Execute methods. There are a number of signatures, as we‟ll see later

in this chapter, and many of them take a VerifierContext.

If the caller provides the context, it will have instantiated the context with this constructor:

C#

VerifierContext(VerifierOnErrorMode pOnErrorMode, Object pCustomContext)

Visual Basic

New (ByVal pOnErrorMode As VerifierOnErrorMode, ByVal pCustomContext As Object)

The VerifierOnErrorMode is an enumeration with two values - Stop and Continue – meaning “Stop verifying

if you encounter an error” and “keep verifying until there are no more verifiers to evaluate”90

.

The “CustomContext” can be any kind of object. It is a mechanism to enable the calling code to communicate

situational information to the verifiers that know how to interpret that information.

If the caller does not provide a VerifierContext, the VerifierEngine constructs one from its own resources:

the VerifierEngine.DefaultOnErrorMode and the VerifierEngine.DefaultCustomContext. The

application could set these defaults when it creates the engine instance; it can revise them at will.

Features of the VerifierContext

Here is the interface of the VerifierContext

C#

public class VerifierContext { public Int64 BatchId { get; } public VerifierOnErrorMode OnErrorMode { get; set; }

public Object CustomContext { get; set; }

90 An individual verifier can terminate the batch even if the OnErrorMode is Continue.

Page 358: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

358 | P a g e

public VerifierResultCollection VerifierResults { get; } public object BatchContext { get; set; } public bool EndOfBatch { get; } public Verifier Verifier{ get; } public VerifierEngine VerifierEngine { get; } }

Visual Basic

Public Class VerifierContext Public ReadOnly Property BatchId() As Int64 public Property OnErrorMode() As VerifierOnErrorMode public Property CustomContext() As Object public ReadOnly Property VerifierResults() As VerifierResultCollection public Property BatchContext() As Object public ReadOnly Property EndOfBatch As Boolean public ReadOnly Property Verifier As Verifier public ReadOnly Property VerifierEngine As VerifierEngine End Class

Let‟s walk through them quickly.

Calling the engine‟s Execute method initiates a new verification “batch” that lasts for the duration of the method‟s

execution. The engine assigns the batch a unique BatchId.

As stated earlier, OnErrorMode returns an enumeration with two values - Stop and Continue – meaning “Stop

verifying if you encounter an error” and “keep verifying until there are no more verifiers to evaluate”. A verifier can

change this value at any time.

We‟ve already met the CustomContext containing an arbitrary object defined by the developer and made available

either when the engine was called or through its DefaultCustomContext property. Observer that the object can

be reset at any time during the batch.

The VerifierEngine adds each verifier‟s VerifierResult to the VerifierResultCollection in the

context. Verifiers can see prior results and take action accordingly91

.

The BatchContext is a means of accumulating and communicating execution state within the batch. It starts null

(Nothing in VB). Any verifier can change it, perhaps depositing useful information for downstream verifiers.

The EndOfBatch starts false. The VerifierEngine will set it to true after it evaluates the last verifier in the

batch. This flag is intended for use by a VerifierEngine.BatchInterceptor, a delegate method called by the

engine after it evaluates each verifier – and once more at the end of the batch when it sets this EndOfBatch flag to

true. The interceptor could perform “batch cleanup” when it sees the flag set true.

The VerifierEngine records the most recently evaluated verifier in the context‟s Verifier property. This

property is aimed at the VerifierEngine.BatchInterceptor which may need to take some action after the

engine evaluates a particular verifier.

The VerifierEngine also registers itself in the context‟s VerifierEngine property. Verifiers don‟t need this –

they know to which engine they belong. The VerifierEngine.BatchInterceptor does not know what engine

is running; it can find out by looking at this context property.

Custom Verifiers

91 They can even manipulate the VerifierResultCollection itself; one hopes they are prudent in doing so.

Page 359: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

359 | P a g e

The Verification library comes with many predefined verifiers that cover the majority of cases. Of course you have

to be able to create your own – and you can do so easily. Keep reading and you will see examples. You can find

these same examples, in context, in the Learning Unit on Verification that ships with DevForce.

Verifier Result

We expect a verifier to render a binary decision most of the time. It‟s usually a pass / fail test. Accordingly, every

verifier returns a VerifierResult with an IsOk property. Either it is or it isn‟t.

More nuanced information is also available but there is always a firm yes or no.

If the validation failed we probably want to display a message to the user92

explaining how it failed. The

VerifierResult.Description contains the message prepared by the Verifier – a message that may have been

translated into the local language and culture.

The VerifierResult.Description comes from the Verifier.Description by default. The phrase “First

Name cannot exceed 30 characters” serves well both as the description of the Verifier and the message to the user

when the entered text exceeds 30 characters.

Customizing the Description

When this is not satisfactory, the developer can customize the message.

Sub-class the Verifier and override the Description property

In this example in which the author wants to drive home the point about keeping the birth date reasonable. The

DateTimeRangeVerifier would be fine if not for the message. So the author fills out the

DateTimeRangeVerifier and then overrides the Description property.

C#

/// <summary>Default Ctor,</summary>

/// <remarks>

/// BirthDate is not required,

/// must be on or after global min date (<see cref="M:MinBirthDate"/>),

/// and before today.

/// </remarks>

public BirthDateRangeVerifier() :

base(typeof(Employee), // Type of the object being verified

Employee.BirthDateEntityProperty.Name, // Property trigger

false,// Non-null value is not required

MinBirthDate, true, // starting min date (inclusive)

DateTime.Today, false) { } // ending max date (exclusive)

public override string Description {

// ToDo: Localize

get {

return "Must be born after " + MinBirthDate.Year.ToString() +

"; No time travellers allowed!";

}

}

}

VB

92 Or perhaps to a log file if we are validating outside of a user interface.

Page 360: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

360 | P a g e

Create a Delegate Verifier

One of the easiest ways to create a new verifier is to create an instance of one of the delegate verifiers as we showed

above. The description is one of the parameters in their constructors.

C#

public DelegateVerifier(String pDescription, VerifierCondition<T> pVerifierCondition) public DelegatePropertyValueVerifier(String pDescription, String pPropertyName, bool pIsRequired, ValueVerifierCondition<T> pVerifierCondition)

Visual Basic

' DelegateVerifier Constructor Public Sub New (ByVal pDescription As String, _ ByVal pVerifierCondition As VerifierCondition(Of T)) ' DelegatePropertyValueVerifier Constructor Public Sub New (ByVal pDescription As String, _ ByVal pPropertyName As String, _ ByVal pIsRequired As Boolean, ByVal pVerifierCondition _ As ValueVerifierCondition(Of T))

Sub-class the Verifier and override the Verify() method

The Verifier.Verify method returns the VerifierResult picked up by the VerifierEngine. This gives you

complete control over the VerifierResult.Description which you can construct dynamically.

This is Verify‟s signature93

.

C#

public abstract VerifierResult Verify(Object pItemToVerify, TriggerContext pTriggerContext, VerifierContext pVerifierContext);

Visual Basic

Public MustOverride Function Verify(ByVal pItemToVerify As Object,_ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) As VerifierResult

Localization and Internationalization

There are two mechanisms for localizing the messages reported through the VerifierResult

1. Use resource files for the message templates

2. Translate the property name that is injected into the template.

For example, the basic message template, “{0} is required”, is ready to use as “PropertyRequired” message. At

runtime we plug “First name” or “Last name” or whatever into the slot reserved by “{0}”.

If the application will be used by non-English speakers, we‟ll want to translate the template and we‟ll want to

translate the property names.

93 Although the method is public and it would seem that you can instantiate all of its parameters, you cannot call it yourself; you

will get an exception if you try. This is deliberate; DevForce can ensure proper verifier execution only within a

VerifierEngine.

Page 361: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

361 | P a g e

Message Templates

DevForce ships with standard error and warning message templates. The developer can replace them with a

completely custom version.

The developer creates the .NET resource files for each language94

. The only requirement is that at least the default

file has an entry for all of the DevForce template keys.

A copy of the DevForce verification resource file is available from IdeaBlade as a starting point for

customization.

Visual Studio generates a strongly-typed ResourceManager class to support these custom files. The developer sets

the ErrorsResourceManager property of each new VerifierEngine to this ResourceManager as shown.

C#

VerifierEngine engine = new VerifierEngine(); engine.ErrorsResourceManager = myResourceManager;

Visual Basic

Dim engine As VerifierEngine = New VerifierEngine() engine.ErrorsResourceManager = myResourceManager

“Property Names”

Most common verifiers apply to a single property and inherit from the PropertyValueVerifier. Their message

templates have a slot for the property name and the verifier knows how to fill that slot with the property name after

it has been translated.

The key to the process is the PropertyNameTranslator. The VerifierEngine has a

PropertyNameToDisplayNameTranslator property that takes a PropertyNameTranslator delegate defined

as follows

C#

public delegate String PropertyNameTranslator( Type pType, String pPropertyName);

Visual Basic

Public Delegate Function PropertyNameTranslator( _ ByVal pType As Type, ByVal pPropertyName As String) As String

The expected implementation takes a type-and-string (e.g. Employee and “FirstName”) and turns it into a translated

string.

Note that type-and-string also defines a TriggerItem. As with TriggerItem, the string is typically the

name of a member of the target type … but it doesn‟t have to be.

With that background we are ready to proceed.

94 The .NET practices for localization are beyond the scope of this document.

Page 362: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

362 | P a g e

All predefined PropertyValueVerifier subclasses within the Verification library observe the following protocol

when preparing a “property name” for insertion into the template:

If the engine has a PropertyNameToDisplayNameTranslator, that method is used to translate the property

name.

If there is no translator, the verifier tries the value of PropertyValueVerifier.DisplayName.

If DisplayName is null, the verifier looks for a .NET DescriptionAttribute adorning the object property.

It there is no such attribute, the verifier uses the property name.

This same protocol can be used within a custom verifier, even one multiple slots for multiple property names and

values. The translator is not limited to translating property names.

Triggers

Evaluation of a Verifier may be triggered by one or more “events”.

“Events” is in quotes because the mechanism, while it feels like an event, does not use the .NET event. The

exact mechanism is introduced here and covered more extensively elsewhere in this document.

Setting a property is the most commonly encountered trigger. Setting Employee.FirstName, for example, could

trigger evaluation of a Verifier that checked if the FirstName string value is present and not longer than thirty

characters.

The Verifier that checks the FirstName string length can be evaluated independently of any trigger. It could be

evaluated during validation of an Employee instance95

.

But it is often a kindness to the user if we validate the first name text at the moment she enters it rather than wait for

the entire Employee object to be evaluated. Accordingly, the developer attaches a trigger to that Verifier – a

trigger bound to the Employee.FirstName property.

Property validation of this kind - a property Verifier with an attached property trigger - is extremely popular. It is

so popular that DevForce provides the PropertyValueVerifier96 and a host of derived verifiers to make it easy

to specify property validation.

One approach is to adorn a property with one of the attribute-based versions of the PropertyValueVerifier as

we do for the FirstName property in the following example.

C#

/// <summary>Gets or sets the FirstName.</summary> [StringLengthVerifier(MaxValue=30, IsRequired=true)] public virtual System.String FirstName { // …

Visual Basic

''' <summary>Gets or sets the FirstName.</summary> <StringLengthVerifier(MaxValue:=30, IsRequired:=True)> _ Public Overridable ReadOnly Property FirstName() As System.String Get '…

A VerifierEngine discovers the attribute and the FirstName property it adorns and then adds a

StringLengthVerifier, triggered by the FirstName property, to its list of verifiers.

95 Evaluation in this situation is called “Instance Verification”.

96 The PropertyValueVerifier and its kin are covered below.

Page 363: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

363 | P a g e

Something similar happens when we add the Verifier programmatically to a list of verifiers that we later add to a

VerifierEngine.

C#

// Add FirstName StringLengthVerifier to a list of verifiers. verifiers.Add(new StringLengthVerifier( typeof(Employee),"FirstName", true, 1, 30));

Visual Basic

' Add FirstName StringLengthVerifier to a list of verifiers. verifiers.Add(New StringLengthVerifier( _ GetType(Employee), "FirstName", True, 1, 30))

Behind the scenes, DevForce constructs a Verifier that can validate the Employee.FirstName property and

arranges for that Verifier to be evaluated when someone tries to set the Employee.FirstName property97

.

That “arrangement” is the trigger.

Adding Triggers Explicitly

The predefined PropertyValueVerifiers and their corresponding attribute versions each add a property trigger to a

property verifier implicitly (which is to say, “automatically”).

When you create your own verifiers, you may want to add one or more triggers yourself. These you must add

explicitly.

It is easy to do with the EntityPropertyDescriptors generated by the Object Mapper98

as we see in this

example:

hireDateVerifier.AddTrigger( EntityPropertyDescriptors.Employee.HireDate )

You can add them by string name too.

hireDateVerifier.AddTrigger(“HireDate”)

This isn‟t type safe and it assumes that the trigger property is a property of the object to be verified as is usually the

case. You can specify the trigger type if you want to do so99

.

C#

hireDateVerifier.AddTrigger(typeof(Employee), “HireDate”);

Visual Basic

hireDateVerifier.AddTrigger(GetType(Employee), “HireDate”)

You may go far with just this much understanding of triggers. On the other hand, you may find you need to dig

deeper and then you‟ll want to know about TriggerItem and TriggerLink.

TriggerItem

The TriggerItem represents the triggering Employee.FirstName property.

97 It will also be evaluated when the program validates the Employee object (that is, during “Instance Verification”).

98 You can extend them to include your custom properties.

99 Or if you have to do so for reasons that will become clear below.

Page 364: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

364 | P a g e

The Employee.FirstName property serves two roles in our example. It is both the value that is validated

by the verifier and it is the “thing” that can trigger the verifier. We have to distinguish between the two.

At the moment, we are interested in the property only in its second role – in its capacity as a trigger.

Imagine that the verifier didn‟t look at the first name. Imagine that it performed some other Employee

validation such as checking to see if the person is old enough to be an Employee. We could still trigger this

verifier every time the user touched the FirstName property. The FirstName property serves in the

second role, as trigger, even though it plays no role at all in the validation.

A TriggerItem is little more than a .NET Type and a string called the MemberName. The string is almost always

the name of some member on that type. If TriggerItem represents a property, the MemberName is the property

name.

While most TriggerItems are properties, it should be clear that we can represent almost any member of a

Type as a TriggerItem. We could trigger evaluation of a Verifier with a method as easily as a

property.

In fact, the MemberName could be an arbitrary string that is not an actual member of the type.

TriggerContext

In the course of evaluating a Verifier, the VerifierEngine calls methods on that verifier.

Remember, the VerifierEngine calls these methods. You do not.

These methods include:

C#

public VerifierApplicability IsApplicable( Object pItemToVerify, TriggerContext pTriggerContext, VerifierContext pVerifierContext); public VerifierResult Verify( Object pItemToVerify, TriggerContext pTriggerContext, VerifierContext pVerifierContext);

Visual Basic

Public Function Verify( _ ByVal pItemToVerify As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) As VerifierResult Public Function IsApplicable( _ ByVal pItemToVerify As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) As VerifierApplicability

Notice that the second parameter is a TriggerContext. The TriggerContext provides the verifier with vital

information about how the verifier was triggered.

Page 365: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

365 | P a g e

The engine does not have to be triggered to evaluate the verifier. It could evaluate an entire instance

without prompting by a trigger100

.

The TriggerContext is null in this situation – a fact the verifier may use to establish that it was not

triggered.

The following table highlights the key elements of a TriggerContext:

Class Member Description

ProposedValue Typically a value entered by the user. The value is not yet committed; if

the trigger is a property, the property has not yet been set to this value.

The ProposedValue is meaningful only when the trigger‟s Timing is

Preset.

By convention, the verifier evaluates the proposed value. If the value is

invalid (per the verifier), the triggering property should discard the

proposed value and leave the current property value intact.

The Entity.BeforeSetValue observes this convention.

Timing One of the TriggerTiming enumerations (Preset, Postset) that indicate

whether the validation occurs before the triggering property performs

its task (Preset) or after it has already performed its task (PostSet).

The ProposedValue is meaningful only when the Timing is Preset.

TriggerItem The TriggerItem that inspired the VerificationEngine to evaluate the

Verifier.

TriggerItemInstance The object that pulled the trigger. The Employee instance is the

TriggerItemInstance in an Employee.FirstName trigger.

TriggerLink

We have neglected the TriggerLink to this point, conveniently confining our attention to the TriggerItem.

As it happens, the TriggerItem alone is insufficient if we are to support a robust validation system. The

TriggerItem tells us what kind of object triggered a Verifier. Now we have to find a way back from the object

trigger to the object being verified.

This is easy when the TriggerItem refers to a property of the object being verified. If the trigger is

Employee.FirstName and the Verifier targets the Employee object, it is obvious that “the way back” from the

property to Employee involves no effort at all: the triggering object and the verified object are one and the same.

We wouldn‟t bother with such minutia unless we had grander plans – and we do. We would like to trigger

evaluation of a Verifier when something happens much farther away.

Let‟s change our example from Employee to Order. Suppose the user increases the quantity of an item on Order, a

change that typically increases the total price of the order.

Imagine that there is a verifier on the Order that constrains the total allowed amount of any order to a maximum

amount, an amount calculated per a rule that factors the role of the user entering the data and the Customer‟s credit

limit. This verifier sits on the Order class.

100 This is called “instance verification”.

Page 366: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

366 | P a g e

We could wait until we validated the entire order before evaluating this verifier. If the change broke the limit, we‟d

tell the user. But it might be better to tell the user right away. It might be better if the change to the

OrderDetail.Quantity property triggered the Order verifier immediately.

OrderDetail.Quantity is not a property of Order. It is one hop away, on the navigation path from

OrderDetail to Order. In other words, to make this trigger work, the VerifierEngine must be able to follow

the path from the triggering change in Quantity to OrderDetail and from there to Order.

Enter the TriggerLink. The TriggerLink includes both the TriggerItem and a method that can navigate from

the triggering object to the object to verify, a method known as the TriggerTargetNavigator. In our order

example, the navigator could be the method that implements the nested property path from OrderDetail to Order.

Verifiers and TriggerLinks

We observed earlier that specifying a StringLengthVerifier for FirstName, simultaneously specifies the

Employee.FirstName as its TriggerItem.

It turns out that we are actually attaching a TriggerLink to the Verifier; the Employee.FirstName is the

TriggerItem contained within that link whose other half is the navigator to Employee.

When we use any of the PropertyValueVerifiers, we implicitly create a verifier attached to a TriggerLink

that refers to the chosen property as its TriggerItem. The DevForce syntax hides the hook-up to make creating the

verifier easy.

Easy things should be easy. But hard things should be possible – and a full appreciation of what is actually

happening can open our eyes to more complex scenarios.

Let‟s take a look at some syntax for adding a TriggerLink to a Verifier explicitly. First, the simple case:

C#

TriggerLink aLink = new TriggerLink( new TriggerItem(typeof(Employee), "FirstName"), // TriggerItem null, false); // Navigation aStringLengthVerifier.AddTrigger(aLink);

Visual Basic

Dim aLink As New TriggerLink( _ New TriggerItem(GetType(Employee), "FirstName"), _ Nothing, False) aStringLengthVerifier.AddTrigger(aLink)

The TriggerItem consists of a Type and a property name, just as we expect. The navigator is null (Nothing in

VB) because there is no navigation necessary from the object that triggers the verifier to the object that is verified –

they are the same object.

The third Boolean parameter is false because the link does not return a collection and therefore cannot return

“multiple targets”. The meaning of this mysterious option will become clear shortly.

We would never actually add a simple property trigger this way. There is no reason to specify the TriggerLink or

even the triggering object‟s type. There is no navigator and the type of the trigger is the same as the type of the

verifier. Instead we would write, in both C# and VB,

aStringLengthVerifier.AddTrigger("FirstName")

Now look at the second case involving Order and OrderDetail:

C#

TriggerLink aLink = new TriggerLink(

Page 367: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

367 | P a g e

new TriggerItem(typeof(OrderDetail), "Quantity"), // TriggerItem "Order", false); // Navigation orderTotalPriceVerifier.AddTrigger(aLink);

Visual Basic

Dim aLink As New TriggerLink( _ New TriggerItem(GetType(OrderDetail), "Quantity"), _ "Order", False) orderTotalPriceVerifier.AddTrigger(aLink)

This time we have a navigator. The navigator is indicated by the Order property, a property of OrderDetail that

returns the Order instance to verify. Apparently DevForce can convert a nested property path into a

TriggerTargetNavigator.

How It Works

Here in schematic form is how Verifiers, TriggerItems, and TriggerLinks come together under the control of

a VerifierEngine when the triggering object and the verified object are different.

Something in the trigger property implementation tells the VerifierEngine to verify101

, supplying it with the

means to identify the TriggerItem.

The VerifierEngine finds a TriggerLink for that TriggerItem and also the Verifier to which that

TriggerLink is attached.

The VerifierEngine extracts the TriggerTargetNavigator and calls it, passing the trigger object as a

parameter. The trigger object is the OrderDetail in our example.

The navigator returns the object to verify (the Order).

The VerifierEngine confirms that the object to verify is of the correct type (i.e., it matches the

Verifier.ApplicableType).

The VerifierEngine executes the Verifier, passing the trigger information (a TriggerContext) as one of

the parameters.

Triggering Multiple Verifiers

We said that the “OrderTotalPrice” verifier consults the customer‟s credit limit when determining if the total price of

the order is valid.

If the user changes the customer‟s credit limit, she could render the order valid or invalid. Not just one order either.

She could change the validity of all of the customer‟s outstanding orders.

We might want to draw attention to this by adding a Customer.CreditLimit trigger to the “OrderTotalPrice”

verifier. Here‟s some syntax:

C#

TriggerLink aLink = new TriggerLink( new TriggerItem(typeof(Customer), "CreditLimit"), // TriggerItem "Customer.Orders", // Navigation true); // true = returns multiple targets orderTotalPriceVerifier.AddTrigger(aLink);

Visual Basic

Dim aLink As New TriggerLink( _ New TriggerItem(GetType(Customer), "CreditLimit"), _ "Customer.Orders", _

101 We‟ll investigate how to engage the VerifierEngine in just a few moments.

Page 368: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

368 | P a g e

True) orderTotalPriceVerifier.AddTrigger(aLink)

Note that this time the third argument of the TriggerLink constructor is True. We had to add an additional argument

to signal that this TriggerLink could return multiple objects to verify102

. The VerifierEngine will execute the

“OrderTotalPrice” verifier for each of the customer orders. If there are twenty customer orders, there will be twenty

VerifierResults.

TriggerLinks and Performance

We typically don‟t worry about how long it takes to set a property. Now that we‟ve introduced triggers that can

provoke a series of verifications, we should pause and reflect.

The navigator in this last example invoked the Orders property of a Customer instance. That Customer may have

thousands of orders, none of them in the entity cache. Calling Customer.Orders in this situation usually means a

trip to the data store. The UI could stall noticeably while DevForce runs out to the server to fetch the orders.

The developer must be aware of this possibility if she is going to write fancy triggers like this one. She may want to

confine it to entities in the cache or only retrieve the orders that are still open103

.

The Customer.Orders property can‟t be changed. Fortunately, there is an alternative.

TriggerTargetNavigator Delegate

In our previous TriggerLink examples we specified the navigator with a nested property path. We could have

used a TriggerTargetNavigator delegate, defined as follows.

public delegate Object TriggerTargetNavigator(Object pInstance); Public Delegate Function TriggerTargetNavigator(ByVal pInstance As Object) _ As Object

It‟s a simple method that takes one object – the triggering object – and returns another object – the object to

verify104

. Here is the same TriggerLink, rewritten to use a TriggerTargetNavigator delegate method called

“aCustomerOrdersNavigator”.

C#

TriggerLink aLink = new TriggerLink( new TriggerItem(typeof(Customer), "CreditLimit"), // TriggerItem aCustomerOrdersNavigator, // Navigation delegate true); // true = returns multiple targets orderTotalPriceVerifier.AddTrigger(aLink);

Visual Basic

Dim aLink As New TriggerLink( _ New TriggerItem(GetType(Customer), "CreditLimit"), _ aCustomerOrdersNavigator, _ True)

102 If we said False, the link would return a single target object – a collection of Order. The “OrderTotalPrice” verifier applies

to a single Order instance, not a collection. There is a type mismatch between the verifier and the (collection) object returned

from the TriggerLink; the VerifierEngine will raise a VerifierException indicating that the verifier‟s execution

failed.

103 There is no point in verifying closed orders.

104 Remember that this object can be a collection of objects. The boolean TriggerLink.ReturnsMultipleTargets property

tells the VerifierEngine whether to verify the items in the collection individually (true) or as a single object (false).

Page 369: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

369 | P a g e

orderTotalPriceVerifier.AddTrigger(aLink)

This method could use DevForce persistence operations to do the navigation but it doesn‟t have to. It can have any

implementation that returns objects that match the verifier‟s target object type, the value of

Verifier.ApplicableType.

Do not use an asynchronous delegate. Validation is not workflow. Validation is an inherently

synchronous operation and the VerifierEngine is not thread safe. The navigator must return an object to

verify; the application must pause until that object becomes available.

PropertyDescriptor Syntax

We have shown the TriggerItem in its “native form” as a .NET Type and a member name.

The PropertyDescriptor alternative may be easier to enter, easier to read, and is certainly more type-safe

because the developer does not have to code the member name as a string.

Here‟s how to add the simple property trigger in a single statement using the PropertyDescriptor notation in

either C# or Visual Basic:

aStringLengthVerifier.AddTrigger(Employee.PathFor(e=>e.FirstName))

Here‟s how to add the TriggerLink with PropertyDescriptor notation.

C#

TriggerItem item = new TriggerItem(typeof(Customer),

Customer.CreditLimtEntityProperty.Name);

orderTotalPriceVerifier.AddTrigger(

new TriggerLink(item, // TriggerItem

Customer.PathFor(c=>c.Orders), // Navigation

True)); // true = returns multiple targets

VB

Dim item As New TriggerItem(typeof(Customer),

Customer.CreditLimtEntityProperty.Name)

orderTotalPriceVerifier.AddTrigger(

new TriggerLink(item, „ TriggerItem

Customer.PathFor(c=>c.Orders), „ Navigation

True)) „ true = returns multiple targets

Non-Property Triggers

We tend to discuss triggers as if they were always property triggers. They usually are. But they don‟t have to be.

It takes a TriggerItem to trigger verification. The TriggerItem consists of a Type and a String called the

MemberName. The MemberName could be any string. Usually it is a property name but it need not be. It could be a

method name. It could be a string with no intrinsic meaning at all.

The VerifierEngine uses the “type-and-string” to find verifiers to evaluate. It is as if the engine had a dictionary

of TriggerItems, each leading to a TriggerLink and each link leading to a Verifier105

. The “reality” of the

MemberName is irrelevant from this perspective.

Any block of code can trigger verification. All it has to do is call a VerifierEngine in a trigger-like way as

discussed in the section “Invoking Verification”.

105 Actually, a TriggerItem could lead to multiple TriggerLinks and each of those links could be attached to multiple

Verifiers. A single TriggerItem can launch an avalanche of verifications.

Page 370: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

370 | P a g e

The DevForce Object Mapper generates property setter code that calls a VerifierEngine in a trigger-like way.

You do the same when you write your own custom settable properties.

You could put the same call logic inside a method. For example, you might trigger Order verification inside

methods that add or remove OrderDetail items so that you can immediately test the effect of adds and deletes on

the total price of an order.

TriggerTiming (Preset, Postset) is a convention that you should follow but can adapt to your purpose. Your

AddOrderDetail method could trigger verification in a Preset manner before adding the new item106

. If validation

fails, the method could discard the item before it did any harm.

VerifierEngine

The VerifierEngine is the primary entry point for verification services.

An application may have any number of VerifierEngines although most will only need one.

Each VerifierEngine contains a list of verifiers and a set of methods that allow collections of these verifiers to be

evaluated sequentially against an instance of a .NET class.

The verified object could be a DevForce business object but it doesn‟t have to be. The object can be of any

concrete type.

Each verifier produces a VerifierResult. The engine accumulates these results in a

VerifierResultCollection as it proceeds and returns the entire collection as its own result.

Adding Verifiers to a VerifierEngine

Verifiers can be added to a VerifierEngine in two ways:

The engine can discover them automatically by inspecting the .NET types for verifier attributes.

The developer can add them programmatically.

The application can combine these methods.

We got a taste of verifier discovery in the “Getting Started” started section. We‟ll cover it in more depth shortly.

Programmatic management of an engine‟s verifiers is straightforward via the AddVerifier and RemoveVerifier

methods. Verifiers can be added or removed from a VerifierEngine at any time.

The engine raises a VerifiersChanged event when verifiers are added or removed. available on the verifier

engine and will inform any subscriber of the addition or removal of any verifier107

.

Verifier Discovery

The VerifierEngine always discovers verifiers in the types it is asked to verify108

. When a VerifierEngine

attempts to verify an instance of a type it has not seen before, it probes the type reflectively, looking for verifiers.

The probing strategy is as follows.

Start with the most senior base class in the type‟s inheritance chain.

106 You must supply a ProposedValue. It can be any kind of object such as the item to be added. It could be null.

107 The event is also raised when triggers are added or removed from a verifier that has been registered in the engine.

108 Automatic discovery is not always a good thing, and developers can disable an engine‟s automatic discovery. An engine with

automatic discovery disabled can still perform discovery when asked to do so.

Page 371: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

371 | P a g e

Look for instances of the VerifierAttribute109 class on members of that base class. These define the

“attributed verifiers”.

Look for a static method decorated with the VerifierProviderAttribute110;

Such a method must take a single parameter of type object – this is the “VerifierProviderContext” – and it

must return an IEnumerable(Of Verifier).

The engine calls the VerifierProvider and adds the Verifier instances returned by that method to its list of

verifiers for the base type.

Find the next class in the type‟s inheritance chain and return to step #2.

Stop when have descended to the type that initiated the discovery process.

We have seen the attribute verifiers earlier.

A VerifierProvider might look like this:

C#

#region Verification

#region GetVerifiers Method

/// <summary>Get Verifiers.</summary>

/// <param name="pVerifierProviderContext">Context in which these Verifiers

are retrieved.</param>

/// <returns>The verifiers.</returns>

[VerifierProvider]

public static IEnumerable<Verifier> GetVerifiers(Object

pVerifierProviderContext) {

List<Verifier> verifiers = new List<Verifier>();

verifiers.Add(GetHireDateRangeVerifier());

verifiers.Add(new BirthDateRangeVerifier());

verifiers.Add(GetBornBeforeHiredVerifier());

verifiers.Add(GetPhoneNumberVerifier(Employee.HomePhoneEntityProperty));

return verifiers;

}

#endregion

#region Hire Date Verifier

/// <summary>Get a GetHireDateRangeVerifier.</summary>

/// <remarks>

/// Demonstrates building a highly focused verifier

/// by encapsulation a standard verifier

/// and its configuration.

/// </remarks>

private static Verifier GetHireDateRangeVerifier() {

Verifier v = new DateTimeRangeVerifier(

typeof(Employee), // Type of the object being verified

Employee.HireDateEntityProperty.Name, // Property trigger

false, // Non-null value is not required

MinHireDate, true, // starting min date (inclusive)

MaxHireDate, false); // ending max date (exclusive)

return v;

109 DevForce provides a number of common verifiers in attribute form all of which descend from VerifierAttribute. The

developer can add custom VerifierAttribute subclasses just as he can add custom Verifiers.

110 Actually, there can be more than one such method in the class and the VerifierEngine will call each one.

Page 372: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

372 | P a g e

}

private static DateTime MinHireDate {

get {

return new DateTime(1990, 1, 1);

}

}

private static DateTime MaxHireDate {

get {

return DateTime.Today.AddMonths(1);

}

}

#endregion

#region BirthDateRangeVerifier inner class

/// <summary>Get the minimum BirthDate allowed.</summary>

private static DateTime MinBirthDate { get { return new DateTime(1900, 1,

1); } }

/// <summary>BirthDate Range Verifier</summary>

/// <remarks>

/// Illustrates changing the error messaging for a particular property.

/// Have to subclass to take control of the messaging.

/// Here the message is statically known so we override

/// <see cref="M:Description"/>;

/// if it were dynamic or if

/// <see cref="T:DateTimeRangeVerifier"/> constructed the

/// message dynamically, we would have overridden

/// <see cref="M:VerifyValue"/> and manipulated the

/// message while creating the <see cref="T:VerifierResult"/>.

/// </remarks>

private class BirthDateRangeVerifier : DateTimeRangeVerifier {

/// <summary>Default Ctor,</summary>

/// <remarks>

/// BirthDate is not required,

/// must be on or after global min date (<see cref="M:MinBirthDate"/>),

/// and before today.

/// </remarks>

public BirthDateRangeVerifier() : base(

typeof(Employee), // Type of the object being verified

Employee.BirthDateEntityProperty.Name, // Property trigger

false,// Non-null value is not required

MinBirthDate, true, // starting min date (inclusive)

DateTime.Today, false) { } // ending max date (exclusive)

public override string Description {

// ToDo: Localize

get {

return "Must be born after " + MinBirthDate.Year.ToString() +

"; No time travellers allowed!";

}

}

}

#endregion

#region Born Before Hired Verifier

/// <summary>Get a BornBeforeHiredVerifier.</summary>

/// <remarks>

/// Demonstrates comparing two property values

Page 373: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

373 | P a g e

/// by creating an instance of a

/// <see cref="T:DelegateVerifier{TVerifiedObject}"/>.

/// </remarks>

private static Verifier GetBornBeforeHiredVerifier() {

// ToDo: localize description

string description = "Must be born before hired.";

DelegateVerifier<Employee> v =

new DelegateVerifier<Employee>(description,

BornBeforeHiredCondition);

v.AddTriggers(Employee.BirthDateEntityProperty.Name,

Employee.HireDateEntityProperty.Name);

v.ExecutionModes = VerifierExecutionModes.InstanceAndOnPostsetTriggers;

return v;

}

/// <summary>

/// The <see cref="T:VerifierDelegate{TVerifiedObject}"/>

/// for the <see cref="M:GetBornBeforeHiredVerifier"/>.

/// </summary>

private static VerifierResult BornBeforeHiredCondition(

Employee pEmp, TriggerContext pTriggerContext, VerifierContext

pVerifierContext) {

if (pTriggerContext != null &&

// We are not checking the proposed value because don't expect to call it

preset

pTriggerContext.Timing == TriggerTiming.Preset) {

throw new VerifierException("BornBeforeHired verifier not implemented for

Preset");

}

return new VerifierResult(pEmp.BirthDate < pEmp.HireDate);

}

#endregion

#region Phone Number Verifier

/// <summary>Get a GetPhoneNumberVerifier.</summary>

/// <remarks>

/// Encapsulates a standard RegexVerifier, subclassed so the description

can be customized.

/// </remarks>

private static Verifier GetPhoneNumberVerifier(EntityProperty

pPhoneEntityProperty) {

return new PhoneNumberVerifier(

pPhoneEntityProperty.EntityType, // Type of object being verified

pPhoneEntityProperty.Name, // Trigger

false, // Non-null value is not required

NamedRegexPattern.USPhone); // Regex pattern to use

}

private class PhoneNumberVerifier : RegexVerifier {

public PhoneNumberVerifier(Type pApplicableType, string pPropertyName,

bool IsRequired, NamedRegexPattern pattern)

: base(

pApplicableType,

pPropertyName,

IsRequired,

pattern

) { }

Page 374: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

374 | P a g e

public override string Description {

get {

return base.Description +

" including area code [e.g., (206)555-1212, 206-555-1212, or

206.555.1212].";

}

}

}

#endregion

#endregion

VB

VerifierProviderContext

Observe that a VerifierProvider method has an object parameter called the “VerifierProviderContext”.

This is an arbitrary object, open to the developer‟s imagination. The VerifierEngine will pass it along to each

provider.

The engine acquires this context object in one of two ways:

From the VerifierEngine.DefaultVerifierProviderContext which the developer must have initialized

before the engine starts its discovery process.

As the second argument to VerifierEngine.DiscoverVerifiers(Type, Object). This is a method that

forces verifier discovery for the given type.

The VerifierProviderContext object could be anything. It could be a pre-calculated list of verifiers for the

type. It could include the VerifierEngine itself so that the VerifierProvider can inspect and manipulate the

other verifiers for this type.

The application must set the VerifierEngine‟s DefaultVerifierProviderContext or call its

DiscoverVerifiers method early.

The engine starts auto discovery as soon as it receives a request to verify an instance of a type. That

discovery could fail or populate the engine with the wrong verifiers if the developer doesn‟t make these

calls first.

Recommended Verifier Loading Approach

We recommend that most applications rely on automatic discover to build up a VerifierEngine‟s list of verifiers.

It is ok to add or remove verifiers from a VerifierEngine programmatically outside of the class being verified but

you should have a good reason for the extra and unexpected complexity.

Some business requirements call for configurable validation rules. Verifiers can be represented in metadata, saved to

storage, retrieved when the application starts, and plugged in to a VerifierEngine.

Configuring New VerifierEngines Consistently

Page 375: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

375 | P a g e

While most applications will have only one VerifierEngine, there are good use cases for having two or more.

Wherever there are multiple engines there arises the need to ensure that they are all configured consistently and

appropriately. We don‟t want a rogue programmer blithely instantiating new engines that lack a

DefaultVerifierProviderContext or are missing some other critical setting.

The application can attach a handler to the static event, VerifierEngineCreated, on the VerifierEngine

class. The event is raised whenever there is a newly created engine. The new engine is passed in the

VerifierEngineCreatedEventArgs so that the handler can configure it.

Invoking Verification

Verifiers do not execute themselves nor can they be executed on their own. They must belong to a (single)

VerifierEngine and rely on that engine to make them do their validation work.

A VerifierEngine doesn‟t verify on its own either. Something has to tell it to verify.

DevForce shouldn‟t perform any operation unless it is asked to do so. Verification is a potentially costly operation.

Perhaps as important, DevForce would not know what to do when it was done verifying.

Only the application developer can know when to verify and what to do with the results.

DevForce does provide an easy way to automate trigger verification of the properties of business objects. The

developer simply launches the Object Mapper and turns Verification on111

. The Object Mapper generates “setter”

code to call the VerifierEngine at the appropriate time.

It is still up to the developer to invoke verification at other key moments in the application such as:

Verification of entities just before they are saved.

Trigger verification of custom, settable properties of business objects.

Verification upon business object fetch or merge.

Trigger verification of non-business objects.

Fortunately, there are .NET events for all of the key business object moments and trigger verification of non-

business objects looks just like trigger verification of business objects.

In every case, the developer calls one of the VerifierEngine.Execute overloads. The public Execute methods

available at this time fall into three “Execution Modes”:

Instance Verification

Preset Trigger Verification

Postset Trigger Verification

We‟ll examine each mode in this following segments. We‟ll learn how calling the VerifierEngine‟s Execute

method determines whether it will perform instance, preset, or postset verification.

Before we do, it is important to remember that we do not call Verifiers; the VerifierEngine does that.

When we tell it to execute in one of the three modes, it will iterate over its internal list of registered verifiers,

evaluating each verifier that is enabled for the current mode.

A Verifier will only be evaluated if its Verifier.ExecutionModes matches the current mode!

For example, if a verifier‟s ExecutionModes = VerifierExecutionModes.Disabled, the verifier won‟t be

evaluated at all, no matter how we call the VerifierEngine.

111 We saw how to do this in the “Getting Started” section.

Page 376: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

376 | P a g e

Keep this in mind as you review the scenarios below.

Instance Verification

The following are the VerifierEngine.Execute overloads for instance verification:

Execute Overload Description

Instance Verification

1 Execute(object pInstance) Validate an instance within the default VerifierContext

2 Execute(object pInstance, VerifierContext pVerifierContext)

Validate an instance within a particular VerifierContext

3 Execute(object pInstance, IEnumerable<Verifier> pVerifiers, VerifierContext pVerifierContext)

Validate an instance with just the given list of

Verifiers112

.

Validate within a particular VerifierContext.

The “Instance Verification” Execute overloads validate an entire instance. The VerifierEngine

finds the Verifiers for the instance type

keeps only those with the Instance flag set in their Verifier.ExecutionModes

sorts them in execution order113

and evaluates them sequentially.

VerifierContext

Every verifier receives a VerifierContext object during its evaluation. The simplest Execute, which accepts

only the object to verify, passes along a VerifierContext constructed by the VerifierEngine. The other

signatures take a custom VerifierContext argument which the engine modifies before handing to the verifiers.

One of the signatures lets you specify which verifiers the VerifierEngine should evaluate. These verifiers must

be registered with the VerifierEngine and their Verifier.ApplicableType must match the type of the

verified object.

When and Where to Verify an Instance

The business requirements dictate when and where to verify an instance.

Many applications provide the ability to validate an entity at any time and then ensure that every entity passes

validation before it can be saved. Accordingly, this author recommends:

Prepare business objects for instance verification

Generate a BaseEntity in the Object Mapper

Make all business objects inherit from this BaseEntity

Write a VerifyInstance method in that BaseEntity

Verify instances in your handler of the EntityManager.Saving event

Make sure you have such a handler on every EntityManager

Iterate through the entities to be saved, calling VerifyInstance on each one

112 All of the verifiers must have been registered with this engine or else the Execute method returns an exception.

113 Verifiers are sorted by Verifier.SortValue ; ties are broken by the order in which they were loaded into the engine

(Verifier.InitializationOrder).

Page 377: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

377 | P a g e

Accumulate the VerifierResults from each call

Cancel the save if there are any VerifierResults.

Report these results to the user.

These are basic techniques taught in the DevForce tutorials, demonstrated in DevForce movies, and incorporated

(albeit in enriched form) in DevForce reference applications such as Cabana.

Here is a simplified example of a VerifyInstance method.

C#

/// <summary>Validate object for all instance Verifiers.</summary> protected virtual VerifierResultCollection VerifyInstance() { return this.VerifierEngine.Execute(this); }

Visual Basic

''' <summary>Validate object for all instance Verifiers.</summary> Protected Overridable Function VerifyInstance() As VerifierResultCollection Return Me.VerifierEngine.Execute(Me) End Function

Observe that each instance has access to a VerifierEngine; this is the VerifierEngine that belongs to its

EntityManager.

Trigger Verification: Preset and Postset

Should we validate a value before we set the property or after we set the property? There is no universally correct

answer to this question.

Preset Triggers

Some bad values should never enter the object. If the object property concerned the dosage level of a drug, we‟d

want to prevent entry of an invalid value. Ten thousand milligrams of something could be fatal. We have to block

that at the moment of data entry. We don‟t want the user to be able to move until the problem is corrected. We

certainly don‟t want that dosage to appear in the business object ever – not even in cache.

This is the right place for preset trigger verification. In preset verification, the VerifierEngine receives a

“proposed value” from the caller. The engine creates a TriggerContext with TriggerContext.Timing set to

TriggerTiming.Preset. It embeds the proposed value in the TriggerContext.ProposedValue. Then it

makes calls on the verifier(s) linked to the trigger, passing in this TriggerContext so that the verifier (a) knows

how it was triggered and (b) the value it should test.

By convention, the code that asks for preset trigger verification should examine the VerifierResultCollection

returned from the engine before doing anything more with the proposed value. If the results collection contains an

errant result – if VerifierResultCollection.AreOk is false – the code should discard the proposed value.

Page 378: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

378 | P a g e

The following are the VerifierEngine.Execute overloads for “preset” trigger verification:

Preset Trigger Execute Signatures Description

1 Execute(object pTriggerItemInstance, String pMemberName, Object pProposedValue)

Perform “preset” validation of verifiers on all objects

that are linked to the pMemberName property of the

pTriggerItemInstance.

The property will be set to the pProposedValue unless

the validation fails.

Verifiers receive the default VerifierContext.

We say that the property caused a “preset trigger

validation”

2 Execute(object pTriggerItemInstance, String pMemberName, Object pProposedValue, VerifierContext pVerifierContext)

Perform “preset” validation of verifiers on all objects

that are linked to the pMemberName property of the

pTriggerItemInstance.

Verifiers receive the given VerifierContext.

3 Execute(object pTriggerItemInstance, PropertyDescriptor pDescriptor, Object pProposedValue)

Perform “preset” validation of verifiers on all objects

that are linked to the given PropertyDescriptor which

translates to a property of the pTriggerItemInstance.

Verifiers receive the default VerifierContext.

4 Execute(object pTriggerItemInstance, PropertyDescriptor pDescriptor, Object pProposedValue, VerifierContext pVerifierContext)

Perform “preset” validation of verifiers on all objects

that are linked to the given PropertyDescriptor which

translates to a property of the pTriggerItemInstance.

Verifiers receive the given VerifierContext.

5 Execute(object pTriggerItemInstance, TriggerItem pTriggerItem, Object pProposedValue)

Perform “preset” validation of verifiers on all objects

that are linked to the given TriggerItem.

Verifiers receive the default VerifierContext.

6 Execute(object pTriggerItemInstance, TriggerItem pTriggerItem, Object pProposedValue, VerifierContext pVerifierContext)

Perform “preset” validation of verifiers on all objects

that are linked to the given TriggerItem.

Verifiers receive the given VerifierContext.

Setting a Preset Trigger

The natural place to trigger a preset validation is inside the setter of the property, before writing the incoming value

into the object. The code should provide the incoming value as the “ProposedValue” parameter. The code may

include a VerifierContext if it will help the triggered Verifier do its job but the context is optional and may

be null.

The VerifierEngine provides the Verifier with a TriggerContext object that (a) alerts the Verifier to the

fact that it was triggered and (b) provides the contextual information it needs to do its evaluation, including the

proposed value in this preset case.

See the “TriggerContext” section for more information.

If the verification fails – if any preset Verifier produces an errant VerifierResult – the property must do

something. The .NET framework development guidelines suggest that it should throw an exception. There is a

VerifierResultException114 for this purpose.

114 Its constructor accepts a VerifierResultsCollection parameter that handlers can interpret and present intelligently.

Page 379: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

379 | P a g e

A VerifierResultException should not terminate the application.

DevForce handles the exception gracefully when it occurs during data binding; see the “Verification and

WinForms User Interfaces” section. The developer must handle a VerifierResultException thrown

outside of data binding.

Entity.BeforeSetValue

The Object Mapper generates an Entity.BeforeSetValue method that adheres to this recommendation

precisely115

. The method is virtual; developers can override it in a base entity class if they want different behavior or

if they want to augment it with other behavior such as error logging.

Postset Triggers

“Life and death” properties are relatively rare. It is usually ok if the property value is invalid while the user is

working with the object. We want the user to know the value is invalid. We want to block every attempt to save

invalid data. But we can tolerate bad values for a while.

For example, the employee‟s home city may be a required value. We may not be able to save the employee record

until we have a complete and valid home address. We want the application to tell us about the omission in time to

correct it.

On the other hand, it isn‟t going to harm anything if it stays blank while the user is entering new employee

information. If the user mistakenly enters the wrong city, she should be able to clear it. She may not know the name

of the correct city; it is better to leave the city blank than to leave the incorrect city in place. This is fine as long as

we prevent the user from saving the address.

Summarizing the requirement:

Permit entry of an invalid value but advise the user of that fact.

Prevent saving of an object with an invalid value and tell the user about that.

The rule – manifested in the Verifier - is the same in both cases. How we validate and what we do with the result

depends upon the context.

We covered the second scenario - block the save – when we discussed “instance validation” above. We want

“postset” triggered validation to handle the first scenario.

“Postset” means that the property has already been set with the incoming, invalid value from the user by the time we

validate. There is no “proposed value” to worry about. We still want to validate the (now current) property value and

tell the user if there is a problem.

115 The appendix discusses the implementation of BeforeSetValue in detail.

Page 380: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

380 | P a g e

The following are the VerifierEngine.Execute overloads for “postset” trigger verification:

Postset Trigger Execute Signatures Description

1 Execute(object pTriggerItemInstance, String pMemberName)

Perform “postset” validation of verifiers on all objects

that are linked to the pMemberName property of the

pTriggerItemInstance.

Verifiers receive the default VerifierContext.

We say that the property caused a “postset trigger

validation”

2 Execute(object pTriggerItemInstance, String pMemberName, VerifierContext pVerifierContext)

Perform “postset” validation of verifiers on all objects

that are linked to the pMemberName property of the

pTriggerItemInstance.

Verifiers receive the given VerifierContext.

3 Execute(object pTriggerItemInstance, PropertyDescriptor pPropertyDescriptor, VerifierContext pVerifierContext)

Perform “postset” validation of verifiers on all objects

that are linked to the pMemberName property of the

pTriggerItemInstance.

Verifiers receive the given VerifierContext.

4 Execute(object pTriggerItemInstance, TriggerItem pTriggerItem, VerifierContext pVerifierContext)

Perform “postset” validation of verifiers on all objects

that are linked to the given TriggerItem.

Verifiers receive the given VerifierContext.

As always we must tell the VerifierEngine to perform verification.

The VerifierEngine will give the triggered Verifier a TriggerContext just as it did for the preset trigger but

this time there will be no proposed value; the verifier may have to fish the value out of the object.

That shouldn‟t be hard. The Verifier typically knows the property it verifies and this property is usually the same

property that triggered verification. A “First Name” StringLengthVerifier that is triggered by input of first

name text will know how to examine the FirstName property of the Employee instance it verifies.

DevForce removes the guess work if the Verifier inherits from PropertyValueVerifier (as

StringLengthVerifier does). Every subclass of PropertyValueVerifier has a virtual

VerifyValue method that receives both the instance to verify and the value to verify.

It is slightly trickier if the instance triggering the verifier is different from the object instance verified. We

encountered such a case when we considered a “TotalPriceVerifier” on Order that is triggered by a change to the

price of one of its OrderDetails.

Fortunately, the Order‟s “TotalPriceVerifier” can use the TriggerContext.TriggerItem.MemberName

(“UnitPrice”) to dig the changed price value out of the TriggerContext.TriggerItemInstance (the

OrderDetail instance).

Relatively few verifiers involve such circuitous triggering. The vast majority of verifiers are PropertyValueVerifiers

whose triggering and verified instances are the same object.

Which leaves us with the small problem of invoking the VerifierEngine at the right time. As this is a postset

trigger, we should call the engine immediately after the line that pushes the incoming value into the trigger object.

Entity.AfterSetValue

Page 381: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

381 | P a g e

That is what the Object Mapper does when it inscribes an Entity.AfterSetValue method into the generated

property code 116

.

What happens if the verification fails? We invoked the verifier for a reason, presumably to alert the user to a

problem.

The AfterSetValue throws a VerifierResultException just as the BeforeSetValue does. DevForce and

.NET handle this just fine if the exception occurs within data binding. The developer must handle the exception if it

occurs anywhere else.

AfterSetValue is virtual so developers can override it in a base entity class if they want different behavior. We‟ll

consider an alternative implementation in the “Verification and WinForms User Interfaces” section.

Remember that you can delay telling the user about invalid input and rely upon instance verification to

catch it just before save. You won‟t need postset triggers if you go this route.

The Role of the Object Mapper

As we just noted, the Object Mapper includes the Entity.BeforeSet and the Entity.AfterSet methods in the

code it generates for properties unless you specify otherwise. It also generates an Args parameter for those methods

that specifies whether verification should be invoked preset and postset. By default, it is invoked in both situations.

Writing Verified Custom Business Object Properties

Developers often write custom business object properties. Such properties are usually ReadOnly, which is to say,

they have a getter but no setter. Trigger validation is a non-issue if there is no setter.

When the developer needs to write a settable property, her code probably should parallel the code generated by the

Object Mapper.

Monitor Execution with the VerifierBatchInterceptor

Some applications need to monitor the progress of a VerifierEngine‟s execution and intervene at certain points.

The VerifierEngine.BatchInterceptor is the way to do it. The engine calls the interceptor after evaluating

each Verifier giving all of the visibility and opportunity it needs.

An interceptor is a method that conforms to the VerifierBatchInterceptor delegate signature:

C#

public delegate VerifierOnErrorMode VerifierBatchInterceptor( Object pInstance, TriggerContext pTriggerContext, VerifierContext pVerifierContext);

Visual Basic

Public Delegate Function VerifierBatchInterceptor( _ ByVal pInstance As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) As VerifierOnErrorMode

Because the interceptor‟s parameters are the same as the parameters of the Verifier methods, IsApplicable and

Verify(), it has the same visibility into the verification process as they do.

116 The appendix discusses the implementation of AfterSetValue in detail.

Page 382: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

382 | P a g e

The interceptor can

see the last verifier evaluated by looking at the VerifierContext.Verifier.

review and edit the accumulating VerifierResultCollection by looking at the

VerifierContext.VerifierResults.

terminate the current batch at any time by returning VerifierOnErrorMode.Stop.

post-process the VerifierResults when the batch is done – because the engine will call it one last time with

the VerifierContext.EndOfBatch flag set true.

The following example shows how one could use an interceptor to curb run-away validations. In this case, it

terminates the batch on the third error:

C#

… VerifierEngine engine = new VerifierEngine(); engine.BatchInterceptor = MyBatchInterceptor; … private VerifierOnErrorMode MyBatchInterceptor( Object pInstance, TriggerContext pTriggerContext, VerifierContext pVerifierContext) { if ( pVerifierContext.VerifierResults.Errors.Count > 2 ) { pVerifierContext.VerifierResults.Add( new VerifierResult(false,"More than 2 errors encountered")); return VerifierOnErrorMode.Stop; } else { return VerifierOnErrorMode.Continue; } }

Visual Basic

… Dim engine As New VerifierEngine() engine.BatchInterceptor = AddressOf MyBatchInterceptor … Private Function MyBatchInterceptor( _ ByVal pInstance As Object, _ ByVal pTriggerContext As TriggerContext, _ ByVal pVerifierContext As VerifierContext) As VerifierOnErrorMode If pVerifierContext.VerifierResults.Errors.Count > 2 Then pVerifierContext.VerifierResults.Add( _ New VerifierResult(False,"More than 2 errors encountered")) Return VerifierOnErrorMode.Stop Else Return VerifierOnErrorMode.Continue End If End Function

Verification and WinForms User Interfaces

Now that the application is detecting invalid data and throwing exceptions, we had better think about how we want

to handle those exceptions and tell the user what is going on.

UI Lockup

The UI is going to lock up the moment the user enters an invalid value into a verified UI control. That is any data

entry control: TextBox, DataPicker, ComboBox, etc. The user will not be able to leave that control until she enters a

value that passes validation – not even to close the form.

Page 383: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

383 | P a g e

In this illustration, the user cleared the “Last Name”. The last name is required. The form displays an error bullet

and prevents the user from moving out of the textbox.

How does the user recover?

If this were a grid, she could press the [Esc] key; it is “standard” for grid controls to restore the previous value when

the user presses “escape.” How many users know that? In any case, this TextBox is not in a grid and pressing [Esc]

does nothing but ring an annoying bell.

The user can press the standard key chord for “undo”: Ctrl+Z. How many users know that?

No, the most users will just keep entering new values until they find one that lets them out of the field.

Needless to say, a UI should apply the “lock up” enforcement technique sparingly. In the author‟s opinion, it makes

sense only for

a value the user must know and is sure to know

a value that must be correct immediately and at all times.

Dosage of a dangerous prescription drug would fit this bill. Few other properties qualify.

Unlock the UI with AutoValidate

Recall that the DevForce Entity.BeforeSetValue and Entity.AfterSetValue methods raise a

VerifierResultException when the property fails validation. This exception bubbles up and out of the property

setter.117

Data binding traps the exception118

and responds by locking up the form. Fortunately, WinForms .NET 2.0 makes it

easy to change this response.

The key is the System.Windows.Forms.UserControl.AutoValidate property which takes one of the

System.Windows.Forms.AutoValidate enumerations.

AutoValidate Description

Inherit Do what the parent UserControl does. The parent is the UserControl

that contains this UserControl.

This is the default for new UserControl instances.

If there is no parent, the value is the default, EnablePreventFocusChange.

EnablePreventFocusChange Prevents the user from leaving the control until the value passes

validation.

EnableAllowFocusChange Validate but permit the user to leave the control if validation fails.

Disable Does not validate. Generally not a good choice.

Inherit is the default value for all new UserControls119

. Inherit means that the UserControl is governed by

the AutoValidate setting of its parent UserControls, the UserControl that contains it.

117 Thanks to the System.Diagnostics.DebuggerNonUserCodeAttribute that decorates the setter.

118 During the data binding Validate event raised when the user attempts to leave the TextBox.

Page 384: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

384 | P a g e

The outer UserControl, typically a Form, doesn‟t have a parent so it is governed by the

EnablePreventFocusChange setting.

If we never change the AutoValidate property on any UserControl, our application is governed by the setting in

the Form which, as we have seen, is EnablePreventFocusChange, the setting that locks up the form. All

UserControls within the Form are inheriting this behavior.

If we change the Form‟s AutoValidate property to EnableAllowFocusChange, the widgets on the Form will no

longer lock up when the setter throws an exception. Neither will widgets on the contained UserControls because

they inherit the parent Form‟s setting.

So the quick answer to UI lockup:

Change the Form‟s AutoValidate property to EnableAllowFocusChange

C#

this.AutoValidate = System.Windows.Forms.AutoValidate.EnableAllowFocusChange; // Can move

Visual Basic

me.AutoValidate = _ System.Windows.Forms.AutoValidate.EnableAllowFocusChange ' Can move

Improving the User‟s Experience

EnableAllowFocusChange and Preset Triggers

AutoValidate.EnableAllowFocusChange works great for property verifiers governed by preset triggers.

The user can move out of the TextBox. Yet she can still see the error bullet protesting the lack of a “last name”.

The TextBox remains cleared so we can see that there is a problem – or rather that there was a problem, that our

intent to clear the name was invalid.

The LastName property itself was never actually changed. A preset trigger prevents the property setter from

updating the object. At the moment there is a discrepancy between the business object property value and the

corresponding widget control display property on screen 120

.

We can see reveal the discrepancy and cure it by scrolling off of the “Nancy” employee and then returning to her.

The TextBox refreshes with her current LastName property value which remains “Davolio”.

119 UserControl is the base class for developer designed screens. System.Windows.Form inherits from UserControl.

Individual “UI widgets” such as TextBox do not inherit from UserControl.

120 We could set the DevForce BindingDescriptor.CancelEditOnError for the binding to LastName to true; this would

immediately restore the TextBox‟s display of the original value. The author dislikes that choice because it obscures what the

user was trying to do by replacing the user‟s data entry. She sees a warning about a problem that is no longer the problem.

Page 385: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Validation Through Verification

385 | P a g e

EnableAllowFocusChange and Postset Triggers

The behavior is different for verifiers evaluated in response to postset triggers.

If we had a LastNameRequiredVerifier and set its ExecutionModes to InstanceAndOnPostsetTriggers,

the LastName property value would be empty, just as it appears in the TextBox. A postset trigger causes validation

after the property has been set with the “proposed value.”

We can confirm this by scrolling off of the “Nancy” employee and then returning to her. The TextBox remains

blank. The current LastName property value is empty.

However, we are no longer aware of the latent validation error. Our application does not validate the Employee upon

display … and that might be a user experience problem121

.

At least it is not a data integrity problem – or doesn‟t have to be. We must assume that the application follows our

advice and ensures that every entity must survive “instance verification” before it can be saved. We further assume

that the application has some mechanism to display errant entities and their problems. Perhaps a simple

MessageBox will do.

This Employee will not survive validation, will not be saved, and the user will be told why.

Questionable User Experience

This approach may be viable if little time can pass between data entry and instance verification.

Some applications attempt a save whenever the user moves off the current screen. The user will never lose sight of

the LastName error bullet and the save effort will reveal all latent problems with this employee.

Many applications delay save and allow the user to move around among entities with pending changes. That‟s how

our tutorial works. Users can make a change to “Nancy”, scroll to “Andrew” and make changes to him, then scroll

back to “Nancy” to continue her updates.

In this kind of workflow, the user may not remember that there is a problem with the “Nancy” object for minutes or

hours. When the application finally tells the user about this problem, the mental context is long gone and the

application will be perceived to be “unfriendly”.

There is another, potentially greater risk. The user may make a critical business decision base upon what is visible

on the screen. That data could be in error. The user won‟t know it if she scrolled off and then back on to the record.

If this risk is serious, the application must behave differently whenever the UI displays a new object – a new

Employee in our example.

Instance Verification Upon Display

One approach would be to perform instance verification whenever the currently displayed object is changed.

121 We could write code to perform “instance validation” whenever the Employee changed. We could capture the VerifierResults

and display them as well as light up bullets next to each widget. The code is not hard to write but it‟s not utterly trivial either.

We‟ll describe an approach that achieves something of that effect using a different technique.

Page 386: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce Silverlight Apps

386 | P a g e

DevForce Silverlight Apps

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

DevForce Silverlight Apps Overview - What is DevForce Silverlight? Creating a DevForce Silverlight Application Silverlight Deployment Steps Questions and Answers Troubleshooting

Overview - What is DevForce Silverlight?

DevForce Silverlight allows you to deliver line of business applications in the browser with the kind of

responsiveness users expect from a desktop application. Developed for Microsoft Silverlight, the browser plug-in

which powers rich application experiences, it allows you to leverage your existing DevForce experience with new

tools and techniques to build serious applications.

A few things to note about Silverlight, and thus about DevForce Silverlight:

Silverlight is inherently n-tier. The client application executes in a sandbox on the browser, and must

communicate with a service to retrieve and save data. The DevForce Silverlight Business Object Server

(BOS) provides that service, and allows you quickly to have a Silverlight application retrieving and saving

to a database, using the domain model and business objects you're already familiar with.

Silverlight is inherently asynchronous. To avoid blocking the browser, Silverlight requires that all service

communications be performed asynchronously. This can be a bit challenging at first, but DevForce

Silverlight provides an asynchronous API very similar to the standard synchronous API, plus additional

features to make asynchronous programming as easy as possible.

In DevForce Silverlight, you have the EntityManager to hold your client-side entity cache and communicate with the

BOS, just like you would in a standard DevForce application. The Domain Model is actually shared between the

two environments, and DevForce handles the movement of your business objects between tiers. You use the

standard EntityQuery syntax to build true LINQ queries, which can be directed against a back-end data source or

against the local DevForce cache. Your queries run asynchronously against back-end data sources, or

synchronously against the local cache.

Key to it all is the shared domain model. The domain model used by the Silverlight application is the same domain

model used on the server, or in any .NET DevForce application: not an anemic object model with an unfamiliar

API. You can add business logic - via custom methods and properties, DevForce property interceptors, and

DevForce verification - to your shared domain model. You can also choose to deploy logic which is applicable to

the client-side or server-side only.

Creating a DevForce Silverlight Application

You can use several different approaches to create a Silverlight application with DevForce:

Page 387: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce Silverlight Apps

387 | P a g e

1. Use the DevForce Silverlight application template.

You can find this project template by choosing File - New Project' or 'File - Add - New Project' in Visual

Studio. The template is in the DevForce folder under both the Visual C# and Visual Basic project types. Its

use will result in the creation of both the Silverlight and web application projects for your DevForce

Silverlight application. From here you can work on UI and domain model features, or reference already

created projects. You'll use the DevForce Object Mapper to create the domain model and the "linked"

Silverlight domain model.

2. Use the DevForce Object Mapper.

In this approach, you start by creating a new model (or opening an existing one). In the "Project Settings"

for the domain model you'll see a check box labelled “Create Silverlight Domain Model Project”; you can

use this as a toggle to choose an existing Silverlight project in your solution, or to create a new one.

Select “New project” and choose "Silverlight Application" as the project type in the resulting dialog. This

will use the DevForce Silverlight application template to create the Silverlight and web application

projects. These new projects will also be set to the selected value in the corresponding “Domain Model

Project” and “Silverlight Project” dropdowns. You can then continue working in the Object Mapper as

usual.

3. Use the standard Silverlight application template.

The standard Visual Studio template for a Silverlight application will create both the Silverlight and web

application (or web site) projects. If you want the web application to host your BOS, you will need to do

the following:

Add EntityService.svc and EntityServer.svc files to the project;

Add all necessary IdeaBlade references; and

Modify the web.config to include the appropriate settings for the BOS.

You can find samples of the EntityService.svc, EntityServer.svc, and web.config files in the DevForce

installation LearningResources\110_Deployment\Snippets\IIS Files folder installed by DevForce. You'll

need to use the Object Mapper to create the domain model and the "linked" Silverlight domain model.

Silverlight Deployment Steps

Please see the Deployment topic document (in the Deployment section of the Learning Resources) for detailed

instructions and information about deploying Silverlight apps.

Questions and Answers 1. What is the "linked" Silverlight domain model?

In order to provide a single "shared" domain model which can be used between application tiers, DevForce

Silverlight creates two versions of the model - one compiled with .NET assemblies and one compiled with

Silverlight assemblies. These two versions actually reference the same code files, and use the "linked" file

feature of Visual Studio so that only a single copy of any file is required. The Object Mapper will perform this

linkage for you: it will generate the domain model files into the .NET project, and then create links to these files

in the Silverlight project.

The result is that the domain model is available to both environments: a Customer class is the same whether it‟s

defined in the client Silverlight application, or the server domain model assembly. One additional requirement

also ensures that the types in your domain model can be shared: both the namespace and assembly names must

be the same for the two assemblies holding the domain model. The Object Mapper also does this for you, so in

most cases you don't need to be concerned with the implementation details.

Page 388: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce Silverlight Apps

388 | P a g e

2. Why is there an app.config in my Silverlight application, since Silverlight doesn't support configuration files?

And why is it an embedded resource?

DevForce Silverlight, like any DevForce application, requires configuration information when starting. To get

that configuration information in DevForce Silverlight you should ensure that a file named app.config is located

in the Silverlight application project and marked as an embedded resource. DevForce, via the Object Mapper

and build-time utilities, will automatically create this file and embed it for you, and keep it up to date, so there's

usually nothing for you to do; just don't delete the file. Probing for configuration in DevForce Silverlight

follows the same probing logic, where applicable, as in a standard DevForce application.122

3. Where is the debug log?

Unfortunately, a "client-side" debug log is not currently provided in the beta release of DevForce Silverlight. A

debug log is generated on the BOS server, but it contains the usual server-side messages. A logging or tracing

facility will be added in a future release.

4. Do I have to host the BOS from IIS? And must it be the same web site that's serving the Silverlight application?

You can still host the BOS from either the console (ServerConsole.exe) or Windows Service (ServerService.

exe) in DevForce Silverlight. You can also host the BOS from a different web site than the Silverlight

application. In both scenarios you need to ensure that a policy file is in place to avoid getting a cross-domain

access error. You'll find a sample clientaccesspolicy.xml file in the

LearningResources\110_Deployment\Snippets\Silverlight folder installed by DevForce, along with a readme

explaining how to deploy the file.

5. Can a single BOS support both Silverlight and .NET client applications at the same time?

Unfortunately it cannot, at this time. Currently, a flag in the config file named “clientApplicationType”

determines whether the BOS will communicate with Silverlight or standard .NET client applications. This flag

is global to the BOS. This restriction may be removed in a future release.

6. How can I bind anonymously typed objects in my Silverlight application?

The DynamicTypeConverter converts anonymously-typed objects to dynamically-typed objects for binding in

Silverlight applications. Use the Convert(IEnumerable) method to convert one or more instances of an

anonymous type to corresponding instances of a DevForce dynamic type.

A DevForce "dynamic type" is a System.Type created dynamically at runtime. Generally the primary use for

this conversion is in Silverlight applications, which do not support data binding to anonymous types. Projection

queries are one common example in which return data will be anonymously-typed.

7. How can I customize the communications channel to the BOS? For example, I need to set higher timeout values

and add security.

The default configuration used by DevForce uses HTTP binding, binary encoding, and a

MaxReceivedMessageSize set to maximum value (2G), with all other attributes defaulting. To override the

DevForce defaults you can add a ServiceReferences.ClientConfig file to your Silverlight application. If found,

DevForce will use this file to configure communcations.

A sample ServiceReferences.ClientConfig is provided with the DevForce installation, in the

Deployment\Silverlight folder. Unless you‟re familiar with these files, it‟s best to copy the sample into your

project and customize that. To use - include the file in the Silverlight application project, and mark it as

“Content”. Remember that both the client and server configurations must be compatible for communications to

122 This logic is documented in an appendix to the “Hello DevForce” chapter of this Developers Guide, entitled “Probing

Sequence for the App.Config File”.

Page 389: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce Silverlight Apps

389 | P a g e

succeed, so you will likely need to modify your web.config file also. The “Deployment\Samples N-tier config

files” folder contains samples showing different communications configurations.

Troubleshooting

1. You attempt to Connect to the BOS from the Silverlight client and receive the exception "An error occurred

while trying to make a request to URI 'http://localhost:9009/EntityService.svc'"

Connection errors can have many causes, but the first thing to check, especially in a new application using the

ASP.NET Development Server, is that the Silverlight application is actually "served" by the web application.

You can see this by looking at the address bar in the browser. If it doesn't start with "http://" then the

application is instead loading from the file system. Why is this a problem? Because, for security reasons, a

Silverlight application cannot make service requests unless served by a web server. In DevForce Silverlight this

means that the application cannot connect to, or make other requests of, the BOS; thus, data cannot be retrieved

from or saved to the back-end data source.

The problem is easily remedied by ensuring that the web application project is always the startup project in your

solution.

2. "No license found after probing all assemblies in the config file - Check for valid probeAssemblyNames in the

config file." Possibly seen when double-clicking the “Error on page” icon in Internet Explorer and viewing the

detailed error message.

The probeAssemblyNames in the app.config embedded in the Silverlight application must be fully qualified

assembly names. If not, since Silverlight is not able to load partial assembly names, no assemblies can be

"probed" and no license found. DevForce will ensure the probeAssemblyName is correct if you set the

updateFromDomainModelConfig setting in the file to either "Ask" or "Yes". This synchronization takes place

at build time.

The fully-qualified assembly name might look something like this:

XML <probeAssemblyName name="FirstSilverlightApp, Version=1.0.0.0, Culture=neutral,

PublicKeyToken=null" />

Probed assemblies are used by DevForce not only for validation of the product license, but also to determine the

location of the domain model classes and for custom interface implementations.

3. "*** License violation *** - 'Distributed BOS' not supported with the current license: StandardEF"

You must have a license for DevForce Silverlight in order to develop Silverlight applications with DevForce.

The Silverlight samples in the Learning Units were created with an SL license key and you'll be able to run the

samples as long as you don't regenerate the domain model. Once you regenerate the model with your license

key, the sample may stop working due to the license violation.

4. I get the following exception when trying to fetch: "Unable to locate type: XX.YY"

This not-so-friendly message may be caused by a type name mismatch between your .NET and Silverlight

domain model assemblies. DevForce will seamlessly transmit entities between the SL and BOS tiers, but it

does this using what is essentially a "shared" domain model. DevForce expects to see entities having the same

fully-qualified type name, for instance "DomainModel.Customer, DomainModel, Version=1.0.0.0,

Culture=neutral, PublicKeyToken=null", in both the .NET and Silverlight assemblies holding the model.

This is why DevForce attempts to keep the assembly and namespace names in sync between the two projects,

since without this type name equality, entities cannot move between tiers. This restriction will likely be

Page 390: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce Silverlight Apps

390 | P a g e

removed in later releases of DevForce Silverlight. To fix the exception, ensure that the assembly and

namespace names of the two projects containing the domain model are identical.

5. Why aren't my breakpoints working?

This has nothing to do with DevForce, but we run into it from time to time. Double-check the Web properties

on the web application project, and ensure that both ASP.NET and Silverlight debuggers are checked.

6. Your application was running initially and then crashes after a few minutes with an exception message such as:

“Object reference not set to an instance of an object.. ---> System.NullReferenceException: Object reference

not set to an instance of an object”.

You may have encountered a problem that occurs when the IIS application pool has recycled. One of the best

ways to insure this does not happen is to create a new application pool that does not recycle on a time limited

basis and then assign your application to that pool.

7. Your application had been running and then crashes after you make a change to one or more of the files in the

application directory. The exception includes this message: “Could not load file or assembly

'App_Web_........”.

You may have encountered a problem that occurs when files in the application folder no longer match the

compiled version located in the “Temporary ASP.NET Files” folder. You can force a rebuild of your

application by deleting the “bin” folder and then replace it with a copy or by running the “aspnet_compiler.exe”

command with the “-c” switch. You can find the command by first browsing to the folder

“%SystemRoot%\Microsoft.NET\Framework\” and then open the v2.0.xxxxx subfolder (the numbers after v2.0

can vary) . Here is an example using the virtual directory name of the application: aspnet_compiler –v

/MyApp -c

8. FIPS Compliance

If your Silverlight application will be served from a web server on which FIPS (Federal Information Processing

Standards) compliance is enforced, you will need to make the following changes to both the web.config and

startup pages.

In the web.config, you must set debug to false when FIPS is enabled. This is true even during development: you

cannot set debug to true with FIPS enabled!

XML

<system.web>

<!--

Set compilation debug="true" to insert debugging

symbols into the compiled page. Because this

affects performance, set this value to true only

during development.

-->

<compilation debug="false">

In the startup page (normally default.aspx), you cannot use <asp:ScriptManager> or any controls that rely on it

since it generates a FIPS error. Therefore, you need to use html or javascript to start the Silverlight application.

The example below is using html which will work in most non-IE browsers such as Firefox:

XML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

Page 391: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce Silverlight Apps

391 | P a g e

<title>DevForceSilverlightApp</title>

<style type="text/css">

html, body {

height: 100%;

overflow: auto;

}

body {

padding: 0;

margin: 0;

}

</style>

</head>

<body>

<object data="data:application/x-silverlight," type="application/x-

silverlight">

<param name="source" value="ClientBin/DevForceSilverlightApp.xap" />

</object>

</body>

</html>

The value in red is the location of your xap file relative to the location of the startup page. If you use an .html

page(ex: index.html) instead of an .aspx page, you will need to delete:

XML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

in order to be compatible with Firefox.

9. How to tell if the BOS is running.

You‟ve received an error message from your client application stating, “The remote server returned an error:

Not Found”. This is a communications error which occurs when the WCF client application is unable to

complete a handshake with the server. There are, unfortunately, a myriad of reasons why this might occur, but

one of the first things to check is if the service is actually running.

You can do this easily: open the web browser and point it to the URL which the client application is using. For

example, if the client app.config contains this...

C#

<objectServer isDistributed="true"

remoteBaseURL="http://localhost"

serverPort="9009"

serviceName="EntityService.svc" />

...then open the web browser to http://localhost:9009/EntityService.svc. If the service is running, you will see a

“Service description” page generated by WCF. If, instead, you see a page showing error information, then you

know the service cannot be started and that your application will be unable to run. Usually the error message on

the page has helpful diagnostic information.

10. Known Issues (Silverlight-Only)

Page 392: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce DevForce Silverlight Apps

392 | P a g e

The Copy Local property on DevForce references in the web project must be set to true for the apps to run

properly. This setting is required to allow the DevForce WCF services (defined in the *.svc files) to be

compiled correctly. If not set, the services will not start, the client application will be unable to connect to the

server, and you will see an error message as follows:

The remote server returned an error: NotFound. If the service is unavailable,

then also make sure that the endpoint bindings match between client and server.

When you begin your Silverlight solution using the DevForce Silverlight Application project template, several

DevForce assemblies are added as references to the web project; and for all, CopyLocal is set to true. However,

if you manually add or modify references, you may see that the property is initially set to false (which is the

Visual Studio default). Always check this property when you see the above error.

Page 393: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

393 | P a g e

WinForm User Interfaces

Features described in the section are included with the DevForce WinClient product, and apply to developers

working with WinForm (not WPF) user interfaces. The features and facilities discussed in this chapter do

NOT apply to Silverlight application development using the DevForce Silverlight product.

WinForm User Interfaces

UI Data Binding NET Data Binding NET v. DevForce WinClient UI Data Binding for WinForms Data Binding with DevForce WinClient UI Designers For WinForms DevForce WinClient Data Binding Architecture Nested Property Paths Data Binding to Data Objects of Any Type When to Use .NET Data Binding Instead When Not to Use Data Binding at All

UI Architecture Nested Property Paths The BindableList(of T) EntityPropertyDescriptors

UI Designers BindingManagerDesigners

More on Third-Party WinForm Control Suites Developer Express “DXperience” Infragistics “NetAdvantage”

DataBinders

Troubleshooting Third-Party Control Suites UI Performance Tuning Large BindingSource loads are Slow

DevForce WinClient Assemblies for WinForm Support

DevForce WinClient includes specific support for building WinForm user interfaces. ControlBindingManagers are

provided to centralize all bindings to a particular business object type on a Form or UserControl. A special subclass

of the .NET BindingList<T> class, the BindableList<T>, provides bi-directional binding refresh, facilitates sorts,

and can be configured for automatic update as the contents of the DevForce WinClient local cache change.

BindingDescriptors, DataConverters, and ViewDescriptors encapsulate your specifications for UI databinding

behavior and facilitate reuse and consistency in your databindings across your user interface. UI designers ease and

speed the layout of the UI view and the setup and configuration of data bindings.

We‟ll detail all of these classes and facilities in this chapter.

Page 394: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

394 | P a g e

UI Data Binding

A primary concern of any UI is the movement of data between a UI control property such as the Text property of a

TextBox and a corresponding value in a data item such as the FirstName property of an Employee object.

We want to display “Nancy” in the TextBox when we she becomes the current Employee. We want to update her

Employee object when the user changes her name to “Sally”.

We could write the code to do this ourselves. We could fill the TextBox when “Nancy” becomes the current

Employee. We could subscribe to the TextBox‟s Leave event and, in our handler, pull “Sally” from the TextBox

to set the Employee‟s FirstName property.

This is called “imperative” programming. It is tedious and error prone and difficult to refactor when we want to

change the process or abstract it from the form. There are times when it is the right approach, but there should be a

an easier and safer way for 90+% of cases … and there is. It‟s called “UI Data Binding”.

“UI Data Binding” describes the mechanism by which UI control properties are “bound” to data item properties.

“Binding” in this context means that data values are exchanged between the UI control property and the data item

property in response to particular events recognized by the Data Binding infrastructure. Some events trigger the

setting of the UI control property; some trigger the setting of the data item property.

The exchanges happen automatically. We don‟t have to write the transfer code.

Our job as programmers is to declare the mapping between each UI control property and a corresponding property

of the data item. We map once and the infrastructure executes according to our plan. This is called “declarative”

programming.

There are objects galore in object-oriented programming and it‟s often difficult to follow what object we‟re talking

about at any given moment. We‟ll follow Brian Noyce‟s convention of referring to the data bound object – the

source and temporary repository of data displayed in the UI – as the data item.

We say “data item”, not “business object”. Our data item examples usually are business objects but they don‟t have

to be. We can bind to any application object including the parent form or control.

NET Data Binding

.NET itself provides the basic DataBinding infrastructure. That infrastructure was greatly improved in .NET 2.0 as

were the design tools to exploit it.

DevForce WinClient builds on .NET Data Binding in ways we will explore later in this section. Most DevForce

WinClient developers working with WinForms never bother learning raw .NET Data Binding because the DevForce

WinClient extensions and adaptation make binding much easier and consistent.

Nonetheless, there will be times when a solid understanding of native .NET Data Binding is helpful or even

essential123

. In this chapter we will cover just a few of those concepts and techniques and, at that, only in the context

of the use cases that require their use or explanation.

Here are a few resources for learning more about DataBinding in as it comes out of the .NET box.

[Noyes]

Noyes, B., 2006, DataBinding with Windows Forms 2.0, Boston, Addison Wesley.

At last a book dedicated to Data Binding in .NET 2.0. Brian Noyes presents the definitive account of the subject

in a clear, concise, professional, and enjoyable read. You need this book; and on this topic, you need no other.

123 Most notably when we must bind to non-data properties of a control (e.g., background color) or bind to .NET and third-party

controls not yet supported by DevForce WinClient.

Page 395: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

395 | P a g e

[MacDonald]

MacDonald, M., 2006, Pro .NET 2.0 Windows Forms and Custom Controls, Berkeley: Apress.

This detailed and lively examination of Windows Forms construction in NET 2.0 is the best WinForms resource

so far. There is plenty of meat – over 1,000 pages - garnished with hard-to-find tips. The chapters include

coverage of tool strips, DataGridView, .NET data binding, sound and video, threading, and interface styles. The

appendix on ClickOnce is a bonus.

[Petzold]

Petzold, Charles, 2006, Programming Microsoft Windows Forms, Redmond, Microsoft Press.

An excellent and approachable introduction to the Windows Forms features new in .NET 2.0. Strips away the

crud generated by the .NET designers so we can see the bare bones, sinews, and muscles. Other books do more

but they‟re also huge; Petzold‟s book is spare and focused.

NET v. DevForce WinClient UI Data Binding for WinForms

There remain serious shortcomings in the base .NET implementation and there are opportunities to wrap and extend

.NET Data Binding that improve the development experience and facilitate proper separation of controller and view

logic.

DevForce WinClient UI Data Binding for WinForms aims to overcome the shortcomings and provide the helpful

extensions. The main points are:

Issue DevForce WinClient Solution

Bugs There are a great many traps in the base .NET implementation. Actual data binding

behavior isn‟t exactly as documented. Events don‟t fire when they are supposed to or

don‟t fire at all. Data aren‟t written to the control or data item as prescribed. Some

controls don‟t follow the rules. We catch and work around many of them so you

don‟t have to.

Missing

implementations Many desirable behaviors only become available when the data item implements the

appropriate interfaces. DevForce WinClient business objects do. The DevForce

WinClient binding collection, BindableList(Of T), fills in many of the gaps.

Inconsistency .NET and third-party UI controls are wildly inconsistent both in their property

names and in their support for the data binding infrastructure. The unwary

developer is in for a long and frustrating voyage of discovery if she sets out all on her

own.

Books and tutorials help. But wouldn‟t it be easier to let IdeaBlade do the grunt

work? We‟ve encapsulated much of the bizarre and “nuanced” behavior inside

consistent and simple DevForce WinClient APIs and DevForce WinClient Control

Binders.

You can always fiddle with the controls directly and even write your own Control

Binders but DevForce WinClient‟s default behavior is usually just what you want.

Nested Properties Text book examples always show binding to simple object properties like

FirstName. What about anEmployee.Address.State.Abbreviation? You don‟t

see that one often – for good reason. It doesn‟t work.

Oh, it works some of the time. But never in grids, and the breakdowns are difficult

to predict.

The DevForce WinClient DataBinding collections take care of this important

problem.

Page 396: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

396 | P a g e

Issue DevForce WinClient Solution

Grids The .NET DataBinding infrastructure applies to whole UI controls. It works well

with loose controls – controls bound to a property of a single data item.

All bets are off for behavior inside a UI control. Container controls – grids especially

–observe different Data Binding rules that are imperfectly applied. Nested

properties, for example are not supported.

DevForce WinClient grid binding managers for WinForms strive to overcome these

deficiencies for each of the supported grids, including the .NET DataGrid and

DataGridView, the DeveloperExpress XtraGrid, and the Infragistics UltraGrid.

Dynamic

Properties Sometimes we need a UI control to respond to something in the data item that is not

expressed – or not expressed appropriately – as a public property.

Our first instinct is to add the needed property to the data item. That may not be

possible; we can‟t add a property to a class we don‟t own (e.g., a .NET class). It may

not be wise to add the property even if we could; proper delegation of responsibility

tells us that we should not add a purely UI property (such as an image) to a business

object class.

DevForce WinClient provides the means to enrich the data item with bindable

“dynamic properties” without altering the data item class itself.

DataConverters .NET 2.0 made it easier to add format and parse logic to a binding but the approach

remains crude and does not encourage abstraction.

Nor does .NET 2.0 provide an easy answer to the need to change the format and

parse behavior dynamically in accord with business or application rules. You have

to wire that up yourself.

Disorganization .NET does little to help you organize your data bindings logically. The standard

approach is to pile data bindings onto the form with no apparent concern for how

groups of bindings serve a common purpose.

We can discover all bindings associated with a BindingSource with the expression:

aBindingSource.CurrencyManager.Bindings

But this is an after-thought at best and the collection returned cannot be

manipulated directly – we can‟t add or remove items for example.

The DevForce WinClient binding managers for WinForms provide an explicit

mechanism for organizing bindings around a specific collection of data items and for

a particular purpose. This is the foundation for later refactorings that facilitate

maintenance and testing.

Reuse Static analysis of an application with Data Binding reveals an immense amount of

duplicate code. The CompanyName property, for example, may appear thirty or

more times over as many screens. In most cases it is bound anew each time with an

exact duplicate (one hopes) of the other twenty-nine parameters.

The DevForce WinClient infrastructure for Winforms – ViewDescriptors in

particular – provide the foundation for a clean refactoring to reusable and testable

data bindings.

Most DevForce WinClient developers succeed admirably without ever dropping down to raw .NET Data Binding.

Nonetheless it is vital to understand that we can do so comfortably – and probably will do so – without rocking the

DevForce WinClient Data Binding boat in the slightest.

Page 397: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

397 | P a g e

Data Binding with DevForce WinClient UI Designers For WinForms

DevForce WinClient UI Designers for WinForms are the easy way to bind data items to loose controls and grids.

The designers can also deposit controls on the “form” canvas, giving them names that conform to our preferred

conventions. The control population feature alone can save hours of tedium.

The UI Designers may be found on the “IdeaBlade DevForce WinClient” tab of the Visual Studio Toolbox. A

typical example looks like so:

Drag the ControlBindingManager tool onto the canvas.

A small icon representing an instance of the ControlBindingManager class appears in the component tray beneath

the canvas.

Open its context menu (right-click with mouse) and see some choices124

.

Launch “Autopopulate”

Pick “Employee” as the entity class to bind.

The Designer offers a list of Employee public properties. We select a few until it looks like so:

The designer has suggested some controls and control names for us. We push “Ok”. The labels and controls appear

on the canvas. We do some cleanup: erase the photo label, re-label the manager, re-size and re-locate the image.

124 The details of this scenario are covered at a sane and leisurely pace in other documentation.

Page 398: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

398 | P a g e

We go back to the toolbox, open the “All Windows Forms” folder, and drag a BindingSource component on to

the canvas. We examine the controlBindingManager1‟s property sheet, find “BindingSource”, and set it to the

new instance, bindingSource1. Our Visual Studio design view now looks like this.

At this point we have controls governed by a ControlBindingManager which looks for Employee objects in a

BindingSource. We don‟t have any employees in that source yet. We‟ll solve that next.

We double click the form; Visual Studio hooks up the form‟s Load event to a Form_Load handler template.

We add a single line to that handler that asks the default EntityManager to get every Employee and store the

resulting list into the data source of our BindingSource instance:

bindingSource1.DataSource = DomainModelEntityManager.DefaultManager.Employees.ToList(); // C# bindingSource1.DataSource = _ DomainModelEntityManager.DefaultManager.Employees.ToList() ' VB

We compile, run, and … there‟s our form.

We can now circle back and add more stuff, some navigation, some buttons, and pretty soon we have an Employee

editor.

DevForce WinClient offers a variety of UI Designers for WinForms that work more or less this same way. Each UI

Designer is a .NET component that runs within the Visual Studio development environment and is accessible from

the Visual Studio toolbox.

A UI Designer generates .NET source code directly into the “Form1.Designer” class file which is the companion to

the “Form1” class file we‟ve been modifying.125

.

Here‟s a peek at the code generated in C#

… // // controlBindingManager1

125 These two files together define the entire “Form1” class. Each file defines a “Partial” class meaning that it contains a part of

the definition of the class. The compiler assembles all the partial class files together into the finished class. “Partial Classes”

is a .NET language feature introduced in .NET 2.0.

Page 399: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

399 | P a g e

// this.controlBindingManager1.BindingSource = this.bindingSource1; this.controlBindingManager1.BoundType = typeof(Entities.Employee); this.controlBindingManager1.Descriptors.Add(new IdeaBlade.UI.WinForms.ControlBindingDescriptor(this.mFirstNameTextBox, typeof(Entities.Employee), "FirstName")); this.controlBindingManager1.Descriptors.Add(new IdeaBlade.UI.WinForms.ControlBindingDescriptor(this.mLastNameTextBox, typeof(Entities.Employee), "LastName")); …

The generated code is not pretty but it works. Usually we just leave it alone unless we wish to learn how DevForce

WinClient writes data binding code. This snippet will make more sense when we cover the DevForce WinClient

data binding architecture for WinForms below.

There will come a time when we have to dig in. The UI Designers are fine for quick, one-off screens that can be

defined statically at design time. On the other hand, we‟ll write the code ourselves if we must change or add data

bindings on the fly such as when a control toggles from read-write to read-only. We may discover that certain

control logic appears repeatedly and want to re-factor. These and other reasons compel us to learn more about

DevForce WinClient data binding for WinForms.

It is not hard. We have the UI Designer output as a guide. We can mix generated and custom code as we deem

appropriate. So let‟s leave Designers behind for now to explore the inner workings of DevForce WinClient data

binding.

DevForce WinClient Data Binding Architecture

In this chapter we unveil the DevForce WinClient data binding architecture that implements the MVC pattern. That

architecture pursues certain goals:

Organize bindings around their datasources.

Strive for order, consistency, and simplicity in the face of heterogeneous controls and objects.

Promote code re-use and easy maintenance.

We‟ll hearken back to these goals as we explain what may seem initially a complex implementation model.

High-Level View

Figure 7 shows a high-level view of the DevForce WinClient WinForm data binding architecture. At the extreme

left side of the figure you see the data source – which for the purpose of data binding, is a business object (no matter

where the business object got its data from). At the extreme right side is the data target: a UI control like a

TextBox, ComboBox, or DataGrid.

Page 400: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

400 | P a g e

Figure 7. DevForce WinClient Data Binding Architecture : High-Level View

Data Source

(Business

Object)

Data Target

(UI Control)DataConverter DataBinder

Formatting

Parsing

DevForce EF

WinForm Databinding Architecture

High-Level View

Validation

A property may not have the same data type in the business object data source that it needs in the UI control. A

DateTime or numeric property, for example, may be targetted for display and edit in a TextBox, which only

understands string values. And even if the UI control designated to display the business object property

accomodates the property‟s data type, the developer may want to permit (or require) it to be entered in a different

form in the control. A social security number, for example, might be stored as 123456789 in the business object, but

be entered as 123-45-6789 in the control.

Because the data may need to take different forms in the source and target, data transformations must take place as it

travels between the two. The transformation (if any) that takes place as data moves from the source to the target is

known as formatting; the transformation that takes place as data moves from the target to the source is known as

parsing. Closely related to parsing is validation. Parsing ensures that the entered value can be transformed into the

data type required by the data source; validation ensures that the transformed value conforms to business rules.

DevForce WinClient WinForm data binding interposes two objects between the source and the target: a

DataConverter and a DataBinder. The DataConverter encapsulates information about formatting, parsing, and

validation requirements. The DataConverter knows the data type of the value it will receive from (and deliver to)

the data source; it knows how to transform that value into a variety of anticipated types and formats; and it knows

those aspects of the data validity requirements that are appropriate for enforcement at the point-of-entry.

The DataBinder, on the other hand, knows about a specific UI control and its requirements. The DataBinder uses

information from the DataConverter to configure the UI control. It also tells the DataConverter what type it needs,

leaving it to the latter to deliver the requested type.

You‟ll learn more about DataConverters and DataBinders as you proceed through this chapter. Right now, let‟s

zoom in just a bit to look at some additional details of the DevForce WinClient DataBinding architecture.

DevForce WinClient WinForm Data Binding Architecture – Zooming In

Figure 8 depicts the key components of the DevForce WinClient binding architecture. A BindingManager collects

BindingDescriptors, each of which describes the binding of a single property to a single UI control (which might

stand alone, or be embedded in a DataGrid column). The BindingManager receives data from a BindableList (often

an EntityList) which collects instances of a single type (usually a business object type). BindingDescriptors

Page 401: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

401 | P a g e

encapsulate specifications about an object type and property; a UI control; and desired UI behavior (controlled by

the DataConverter).

Figure 8. DevForce WinClient Data Binding Architecture: Zooming In

The ViewDescriptor, you will note, marries the behavioral specs encapsulated in a DataConverter (which are

specific only to a particular simple data type, like a String or a DateTime) to a specific property from a specific type

-- usually a business object type. You can, in other words, use it to fix the UI behavior of the Employee.BirthDate,

or any other specific property, from your business model. You can then reuse the ViewDescriptor as often as desired

to present consistent property-specific UI behavior across your entire application, regardless of the number of

different contexts in which that property needs to be exposed. You have but one specification to create and

maintain.

That‟s the quick summary of DevForce WinClient databinding. Now let‟s explore it in more detail.

DevForce WinClient Data Binding Definition Process

At the macro level data binding definition is a three step process

Set up one of the kinds of DevForce WinClient Binding Manager

Add binding descriptors to that manager

Create a BindingSource and assign it to the manager

At run-time, we set the DataSource of the BindingSource and fill that DataSource with data items – probably

business objects. We may subsequently tune the bindings as the application and session activities require.

The ControlBindingManager

We‟ll delve into these concepts by beginning with the ControlBindingManager.

We rely on a ControlBindingManager to help us manage the UI controls on a form or other container (user

control, panel, etc.) that displays a single data item. That data item could be an Employee object, one among a list

of Employee objects selected for display or editing.

We can work with only one Employee data item at a time in such a container. That one data item is the “current”

data item.

A dialog devoted to editing a single employee typically features several “loose controls” layed out in some

meaningful pattern across the canvas of the “form.” Each such control can display a single value of a single property

of the “current” employee.

The TextBox is a good example of a loose control. Its Text property can display a string associated with a single

property of one object – an employee first name for example.

Page 402: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

402 | P a g e

A ComboBox is also a loose control. It displays a list of potential values in its drop-down but only one of those

values belongs to the current data item and it is bound to the value accessed by a single property of the current data

item. For example, the value of the State property of the current employee‟s home address, when bound to a

ComboBox, appears as the selected item in the list of fifty states presented in that ComboBox.

The ControlBindingManager manages the entire collection of Employee data item bindings to these loose

controls. It can manage bindings to any number of controls, but all must be bound to employee objects in a single

BindingSource, the BindingSource of Employee objects edited in this “form.”

The managed controls may all belong to the same vendor control suite (e.g., .NET), or may belong to different

vendor control suites. Specifically, you can mix and match .NET controls with Developer Express or Infragistics

controls (or other controls for which you have written custom binders).

Grid Binding Managers

A grid is a special kind of container control with its own data source. It is a tabular container of other controls, one

control per column. Each row represents a single object in the data source. Each table cell displays a property of the

row object in the column‟s control.

The value in a cell can be data bound to a property in a data item just as with loose controls. The collection of all

such bindings falls under the care of a DevForce WinClient binding manager.

The ControlBindingManager is not a candidate for this job. There are a number of peculiarities to the grid

binding process that necessitate a distinctive kind of binding manager, a grid binding manager.

A grid control displays many data items simultaneously. Accordingly, the grid data binding mechanism

must accommodate binding to properties of more than one data item at a time.

The .NET grids we‟ve seen require that all column controls belong to the suite offered by the grid vendor.

We can‟t mix .NET controls with DeveloperExpress controls in either a .NET or DevEx grid.

Our ability to bind to a grid is often constrained. Outside the grid, we may choose from a wide spectrum of

the vendor‟s controls. Inside the grid we are limited to just a few types of column control.126

Column controls may look like their loose control cousins but they are usually crippled in annoying and

peculiar ways.

The data binding rules inside a grid differ from the data binding rules for controls outside the grid. There

are different binding events, different format and parsing behaviors, and different phases in the value

editing cycle.

Grid architectures and APIs differ markedly from one grid vendor to the next.

Consequently grid data binding is considerably more challenging than data binding of simple controls and it is

infeasible to write a single binding manager that can handle every grid of every vendor.

DevForce WinClient offers multiple grid binding managers, one per supported control suite. There are four such

managers at this writing:

126 Some grid vendors enable us to develop new column control types that incorporate controls otherwise available only in loose

form. The .NET DataGridView is especially flexible in this regard. We can extend DevForce WinClient to accommodate

these new column controls.

Page 403: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

403 | P a g e

DataGridBindingManager (for the now deprecated .NET 1.1 grid)

DataGridViewBindingManager (.NET 2.0 grid)

XtraGridBindingManager (Developer Express “DXperience XtraGrid”)

UltraGridBindingManager (Infragistics‟ “NetAdvantage UltraGrid”).

While the differences between the ControlBindingManager and these grid binding managers are important, the

fundamental concepts are almost identical for all of them – a consistency intended by DevForce WinClient‟s data

binding abstraction.

Therefore, we can continue to explore the DevForce WinClient data binding architecture using the

ControlBindingManager as our model with only occasional nods to the specific character of a grid binding

manager.

A Guided Walk Through the DevForce WinClient DataBinding Architecture

In the next few pages we‟ll take a stroll through the menagerie of the DevForce WinClient Data Binding

Architecture, identifying the creatures we encounter as we walk through the process of building a “form”.

The ControlBindingManager Revisited

We just met the ControlBindingManager. It manages a collection of bindings between UI controls and the current

data item in a collection of bound data items.

A ControlBindingManager can only manage bindings to data items of a single bound type. The data items may be

derived from that type (as we‟ll discuss later) but they must “belong” to the same common type. One of our first

steps when creating a binding manager is to specify its type127

.

We can set the ControlBindingManager‟s BoundType property as in

mEmployeeCbm.BoundType = typeof(Model.Employee);

Or we can specify it in the manager‟s constructor

mEmployeeCbm = new ControlBindingManager(typeof(Model.Employee));

Do set the BoundType early in the definition of the binding manager; it is not something to change later.

BindingDescriptorCollection

We‟ve said that the ControlBindingManager manages data bindings. It would be more precise to say that it

manages a collection of BindingDescriptors, each of which is responsible for a data binding.

We can access that collection of descriptors via the manager‟s Descriptors property as in

ControlBindingDescriptorCollection descriptors = mPageCbm.Descriptors;

We can add and remove descriptors from this collection at will. So what is a descriptor?

BindingDescriptor

A binding descriptor defines the binding between a specific UI control on the form and a particular property of a

data item.

127 The type need not be a DevForce WinClient business object. It can be any object in any referenced assembly.

Page 404: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

404 | P a g e

We‟ll unpack the components of a binding descriptor in a moment; here‟s a preview to get the sense of how we

could create one.

ControlBindingDescriptor aDescriptor; aDescriptor = new ControlBindingDescriptor( mFirstNameTextBox, typeof(Employee), "FirstName");

We see the essential ingredients are:

a control

the type of the data item

the name of the data item property

We could add this descriptor to the ControlBindingManager‟s collection.

descriptors.Add(bd);

We can create a descriptor and add it to the collection in a single line.

aDescriptor = descriptors.Add(mFirstNameTextBox, "FirstName");

Observe that:

We omit the data item type because it must be the same as the bound type of the

ControlBindingManager to which this descriptor collection belongs.

The Add method returns the binding descriptor; we may want to adjust some of its properties before adding

another.

In words, we have a ControlBindingManager managing multiple data binding definitions that relate properties of

UI controls to properties of an object of a specific bound type.

Architecture Summary #1

Our Data Binding Architecture diagram at this point looks like this:

We‟ve mapped a group of UI controls to the properties of a single bound type, all under the care of binding

manager. We‟re at roughly this stage when we emerge from the UI Designer.

Next we associate this mapping with a container of data items, the BindingSource.

BindingSource

Data binding in action involves marshalling data between UI control properties and the properties of actual data item

instances. A ControlBindingManager, defined for Employee objects, at some point must be given some real

employee objects to bind. Those objects are, collectively, the datasource for the binding manager.

The ControlBindingManager needs access to this datasource but we don‟t specify it directly128

. Instead we

specify a BindingSource that, in turn, holds our datasource.

128 We used to in .NET 1.1, before the advent of the BindingSource.

Page 405: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

405 | P a g e

We can set the binding manager‟s BindingSource through a property:

mEmployeeCbm.BindingSource = mEmployeeBindingSource;

We might have set it when we created the ControlBindingManager as in

mEmployeeCbm = new ControlBindingManager(mEmployeeBindingSource);

A BindingSource must contain objects of the same type, just like a binding manager. Therefore, the newly

constructed mEmployeeCbm can infer its BoundType from its BindingSource.

.NET 2.0 introduced the BindingSource as the preferred means to maintain collections of data items. The

BindingSource is a smart collection that does far more than hold data items. For example, it keeps a pointer to the

“current” data item and provides methods for moving the pointer up and down the list; each move causes a new item

to become “current” and, in the process, raises a host of events leading to an update of the UI. The programmer can

sit back and enjoy the show.

We access the collection of data items in the BindingSource by means of its DataSource property. A DevForce

WinClient application should set the BindingSource.DataSource as soon as possible, even if there are no items in

that DataSource. One approach would be to set its DataSource during construction.

Consider the following:

mEmployeeEntityList = new EntityList<Employee>(); mEmployeeBindingSource = new BindingSource(mEmployeeEntityList, null); mEmployeeCbm = new ControlBindingManager(mEmployeeBindingSource);

We are

Creating an empty EntityList to hold the Employee data items

Creating a BindingSource based on this EntityList; the BindingSource discovers its bound type from the

EntityList129

.

Creating a new ControlBindingManager with an Employee BoundType and a BindingSource called

mEmployeeBindingSource.

This is not a typical initialization sequence but it makes the essential points about the relationships among

DataSource, BindingSource, and binding manager.

BindableList DataSource

A DevForce WinClient binding manager requires a list datasource whose contents are all of the same type (or

derived from the same type) as the binding manager‟s bound type.

We must provide a list datasource. If we only need to bind to one data item, we‟ll turn it into a one item list as in

mEmployeeEntityList = new EntityList<Employee>(new Employee[] { anEmployee });

The datasource list should be of a type derived from the DevForce WinClient BindableList<T>130

in order to take

full advantage of DevForce WinClient Data Binding features such as nested and dynamic properties.

EntityList<T> is a typical choice in DevForce WinClient applications because it both derives from

BindableList<T> and holds DevForce WinClient business objects.

129 The “null” parameter means there is no DataMember which, in turn, means that the BindingSource finds its contents

in the DataSource. We‟ll consider the “DataMember” when we discuss BindingSource chaining in Master / Detail

scenarios.

130 If we don‟t supply such a list, DevForce WinClient will wrap the list in a BindableList<T> where T is the type of object

in the list. We cover BindableList<T> in detail elsewhere in the chapter.

Page 406: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

406 | P a g e

Filling the DataSource

We don‟t have to fill the datasource with data items while we‟re setting up the bindings. In fact, this is rarely a good

idea. We generally don‟t know which items to display until after the application starts, the “form” is loaded, and

we‟ve given the user the chance to tell us what he wants to see.

Suppose the user filled in a filter form that we resolved into a query. We might see code such as.

// Get selected employees and sort them. mEmployeeEntityList.ReplaceRange(pm.GetEntities<Employee>(filterQuery); mEmployeeEntityList.ApplySort("FullName", ListSortDirection.Ascending, false);

Note that we used the ReplaceRange method.

Do not re-assign the EntityList as in

// Wrong!!! Don‟t do this !!!. mEmployeeEntityList = pm.GetEntities<Employee>(filterQuery);

This code changes the mEmployeeEntityList object reference. Meanwhile, the BindingSource.DataSource

is still holding on to the previous entity list. The UI will show the Employees in that old list rather than the ones we

fetched into mEmployeeEntityList!

Do not re-assign the BindingSource.Datasource as in

// Wrong!!! Don‟t do this !!!. mEmployeeBindingSource.DataSource = pm.GetEntities<Employee>(filterQuery);

This “works” in the sense that the UI will show the newly retrieved employees. But again we‟ve changed the

collection object that was the BindingSource‟s DataSource. This change can cause massive disruption to the data

bindings. This can cause DevForce WinClient to break and remake all of the bindings when it is managing the

BindingSource (as it is in our example).

If the binding manager is a grid binding manager, the end user‟s column adjustments (columns widened or moved,

for example) could be lost.

The effects are rarely fatal but they are disruptive and annoying to the users.

Do use ReplaceRange()

Architecture Summary #2

Our Data Binding Architecture diagram at this point looks like this:

This is where we are after we‟ve mapped controls to a bound type, added a BindableList, and populated that list with

business entities using the EntityManager. We could be looking at a filled in form right now.

Page 407: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

407 | P a g e

BindingDescriptors Revisited

BindingDescriptors bind to the “data” property of a control

The acute observer may notice a slight discrepancy between the characteristics of a .NET Data Binding and those of

a binding descriptor.

A .NET data binding can bind a data item property to any public property of the control. We usually bind to the

control‟s “data” property.

Text is the “data” property of a TextBox. For example, mFirstNameTexBox.Text returns a string bound to the

FirstName property of an Employee data item

A DevForce WinClient binding manager assumes that you want to bind the “data” property of the control so we

don‟t have to specify it131

. This saves both time and heartache because the name of the “data” property is not always

the same from one control to the next.

Binding to non-data properties of a control

.NET data binding itself supports binding to almost any UI control public property. We can bind to the background

color of a TextBox if we want to. Of course this presupposes that there is corresponding property of the data item

that returns a color appropriate for this purpose. Such properties are rare in most application objects, and even rarer

still in business objects.

Rare or not, we may want to bind to one of these other control properties. The control‟s ReadOnly or Enabled

property is a common target for data binding.

Also, as we‟ll learn, while the data item may not have an appropriate bindable property, we can simulate such a

property with the DevForce WinClient “dynamic property” feature.

Fortunately, we can have both .NET data bindings and DevForce WinClient data bindings in the same “form”

without conflict. If our Employee had an OutOfRangeBirthDateColor property, we could add a line to our “form”

initialization method such as:

mBirthDateDateTimePicker.DataBindings.Add( "BackColor", EmployeeBindingSource, "OutOfRangeBirthDateColor");

DataConverters

A data converter massages data on route between the object property and the control property. It does so by means

of the .NET data binding Format and Parse events.

The Format event fires just as the object property data are about to be handed to the control property.

We handle the event if we care how our data are displayed. We have many choices as illustrated in these examples:

The Parse event fires when data will flow in the reverse direction, that is, just as the changed input in the control is

about to be handed to the object property.

131 In fact, we couldn‟t specify that property if we want to do so. A binding manager can only bind to the “data” property of a

control, as we will shortly discusss.

Page 408: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

408 | P a g e

Parsing the input means converting it into a format acceptable to the data type of the receiving property. The parsing

process is closely related to the formatting process. If we used a regex expression to format our data, we probably

could use the same regex expression to control and parse the user‟s input.

Were we confronted with input as shown below, we would accept the first, reject the second, and, depending on the

sophistication of our parsing logic, might accept the third.

After successful parsing, we could pass the parsed product directly to the property, confident that we would not

crash the application due to data type incompatibility. The property then should determine if the input is acceptable

from a business standpoint. In other words, having parsed the input, we should now validate it.

In principle, the object alone should validate the data. In practice, the user experience is terrible if there is a lengthy

delay between user input and validation. Such delays are among the principle frustrations of browser-based

applications.

Benefits of DataConverters

DataConverters abstract the management of formatting and parsing operations into an object.

This abstraction works for loose controls and for grid columns; you don't have to approach them differently.

The abstraction works for controls of different types and from different vendors. There is a great deal of variation in

the databinding syntax and behavior among UI controls; events often don't fire (or fire properly). In DevForce

WinClient, we have gone to considerable effort to discover and do the best thing for each control.

The DataConverter unifies separate but related concepts of Editability, Display (format), Parsing (valid for a type),

and Validation (valid from a business rules perspective).

DataConverter objects can be pre-configured and held in a library prior to their use in any actual data binding. By

contrast, if you hook up the events yourself (or add them to the data binding expression), you must wait until you

have such a binding to do so. With a DataConverter, you configure and hold it; you retrieve and apply it when and

where appropriate.

By applying the behavioral specifications encapsulated in DataConverters to specific properties of specific business

objects – for which marriage DevForce WinClient supplies the ViewDescriptor object -- you can pre-define UI

behavior on a property-by-property basis and then apply it with absolute consistency across your entire UI – not

matter how complex that UI becomes.

The point of all of this is to facilitate development of scalable UI's that are consistent throughout and easy to

maintain.

The base DataConverter class

There is a base DataConverter class. We can assign an instance of it to almost any ViewDescriptor.

It is very flexible. It can format and parse for properties returning most data types. It works with virtually all of the

single value UI controls.

The base DataConverter class is also pretty limited. Although we can set editability, we are obliged to accept its

default formatting and parsing behavior and there is no validation option. It is, after all, only a base class.

Page 409: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

409 | P a g e

Many standard data converters

DevForce WinClient ships with a large number of specialized data converter classes that inherit from

DataConverter. Among them are BooleanConverter, DateConverter, DateRangeConverter,

NumericConverter, NumericRangeConverter, RegexConverter, TextConverter, … the list goes on.

The converter‟s object property data type

As the names suggest, these converters translate to and from a particular object property data type, the converter‟s

“base data type”. When we add a data converter to a ViewDescriptor, the converter‟s base data type must be

compatible with the data type of the ViewDescriptor‟s object property. For example, a binding to the employee

BirthDate or HireDate properties would likely use a data converter with a DateTime base type. The program

might crash if we used a text data converter.

The converter‟s control property data types

A rich data converter can format to and parse from a variety of control property data types. A converter that supports

many control property types can support many controls. The DataConverter class is especially accomplished in

this respect.

A data converter can only be used with a compatible control. If we bind BirthDate to a TextBox, we must use a

converter that can translate between DateTime and string because that‟s what the TextBox control‟s display

property expects. If we bind to a Calendar control that takes a DateTime input, the converter should be able to pass

the date straight through.

The developer of a data converter anticipates the controls we might want to bind to and provides suitable

conversions from the base data type.

Control-specific data converters

A data converter may be able to support many controls or it may be dedicated to just a few. It might specialize in a

single control or a small family of controls to take advantage of the special properties of those controls.

The ListConverter is an example of a control-specific data converter. It specializes in the controls, such as

ListBox and ComboBox, that let the user pick one entry from a list of choices.

These list controls have distinctive properties such as Datasource, DisplayMember, and ValueMember. The

ListConverter recognizes these properties and exposes them to the developer for configuration. The developer

sets them, statically or dynamically, and leaves it to the ListConverter to manage the user interaction.

Custom data converters

Developers can create their own data converters. The easiest way is to sub-class an existing converter and modify it

to suit our needs. Alterations could include new validation checks, different formatting, more robust parsing, or

better control over the visual aspects of the bound UI controls.

A more challenging approach is to write an entirely new data converter, either by sub-classing from

DataConverter or, bolder still, by implementing the IDataConverter interface.

Data converters and re-usable code

This discussion highlights another way the DevForce WinClient data binding architecture promotes re-usable code.

We shouldn‟t have to write individual custom event handlers for each binding when we can see commonalities

across a wide range of cases. DevForce WinClient captures those commonalities in the data converter. As we add

new binding descriptors (or ViewDescriptors) to our UI, we‟ll use one or another flavor of data converter repeatedly,

Page 410: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

410 | P a g e

configuring each converter instance to meet the particular requirements of the object properties to which we will

bind.

ViewDescriptors

A ViewDescriptor marries a DataConverter to a specific property of a specific business object:

The ViewDescriptor describes binding behavior in a completely portable manner for its target property. While a

binding descriptor is tied to a particular control on one form (or to a single column of a particular grid), a

ViewDescriptor is independent of any UI control and can be reused across any number of data bindings to any

number of different UI controls. For a given business object property, you need only describe the desired behavior

once.

ViewDescriptor Catalogs

With ViewDescriptors we can programmatically describe how a specific property of a business object type

should be rendered by the UI before we layout a single screen. We can standardize the visual treatment of objects

and object properties.

Inside the ViewDescriptor

The ViewDescriptor captures four pieces of view information:

the type of the object to bind

the property path from that type which leads to the property that will be bound

the display name for this property

the format, parse, and validation information (which is encapsulated in a DataConverter)

ViewDescriptor‟s Property Path

Digression: hiding the ViewDescriptor

Recall earlier that we can define a binding descriptor without explicitly referencing the embedded

ViewDescriptor. We showed a constructor with parameters for specifying the control, object type, and property

path.

Page 411: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

411 | P a g e

The data converter is so important that DevForce WinClient exposes it both as a property of the binding descriptor

and as a parameter of a binding descriptor constructor overload.

C#

TextConverter aDataConverter = new TextConverter(IdeaBlade.UI.Editability.Optional, 40);

ControlBindingDescriptor aControlBindingDescriptor = new

ControlBindingDescriptor(mLastNameTextBox, typeof(Employee), "LastName", aDataConverter);

| | | |

Control ObjectType Property Path DataConverter

VB

Dim aDataConverter As New TextConverter(IdeaBlade.UI.Editability.Optional, 40)

Dim aControlBindingDescriptor As New ControlBindingDescriptor( _

mLastNameTextBox, GetType(Employee), "LastName", aDataConverter)

| | | |

Control ObjectType Property Path DataConverter

Internally, DevForce WinClient derives a display name from the property path so that all elements of the

ViewDescriptor are complete. It is as if the model looked like this:

To create the binding descriptor using an explicit reference to a ViewDescriptor, you would instantiate your

ViewDescriptor and use a different overload of the ControlBindingDescriptor constructor:

C#

ViewDescriptor aViewDescriptor =

new ViewDescriptor(typeof(Employee), "LastName", aDataConverter);

ControlBindingDescriptor aControlBindingDescriptor =

new ControlBindingDescriptor(mLastNameTextBox, aViewDescriptor);

| |

Control ViewDescriptor

VB

Dim aViewDescriptor As New ViewDescriptor(GetType(Employee), "LastName", aDataConverter)

Dim aControlBindingDescriptor As New ControlBindingDescriptor( _

mLastNameTextBox, aViewDescriptor)

| |

Control ViewDescriptor

The ViewDescriptor constructor used above does not call for the display name, but other overloads are available that

do, and the DisplayName property, along with others, can of course be set after the ViewDescriptor is instantiated.

Formatting and Parsing

The careful reader observes that the binding instruction specifies the name of a property of a data source object type

(the “FirstName” property of an Employee) and the name of a property of a specific UI control (the “Text”

property of a TextBox). .NET uses reflection at run-time to discover the actual properties involved. See [Petzold,

262 ff]

Page 412: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

412 | P a g e

Take note! The compiler is unable to help us with standard .NET data binding because it cannot know until runtime

if those properties actually exist for the data source or the UI Control.

For now we shall keep with the fiction that we are binding to the properties themselves.

BindingSource - Binding to a List of Objects

Returning to our example, we can load the DataSource of the BindingSource with Employee objects and

redefine the binding like so:

myTextBox.DataBindings.Add("Text", myBindingSource, “FirstName”)

CurrencyManager – Positioning within the list

At any given moment, all such data bindings channel data from a single data object within that DataSource, the

object that the BindingSource says is Current.

A typical UI would have some mechanism for moving the position up and down within the list. The .NET

BindingNavigator is a convenient choice. We can bind it to the same BindingSource:

myNavigator.BindingSource = myBindingSource

Clicking the buttons moves us an object at a time within the Employees of the DataSource.

Format and Parse

The Format event is fired in two ways: (1) when the data object becomes current so that the binding can initialize

the control and (2) when there is a change to the bound property of the current data object (we‟ll see how that works

shortly). Each Format event handler receives a value from the bound data object property, formats the value, and

passes the formatted value on to the waiting control.

The Parse event is fired after user input. From time to time we have discovered specific UI controls that do not fire

the event properly. We do our best to insulate you from such aberrant behaviors.

when the user changes focus to another control. The Parse event receives data from the control, parses it, and

passes the parsed value on to the data object property if all went well.

The following diagram summarizes these flows.

Data Binding v. Persistence

In most UIs we bind controls to business objects. It's important to remember that changing an in-memory business

object and updating the persistent data source are different operations.

Page 413: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

413 | P a g e

When the user changes the employee‟s first name, data binding will update the employee entity‟s FirstName

property. Neither .NET nor DevForce WinClient update the corresponding “FirstName” column for that employee‟s

row in the Employee database table. Not automatically. The persistent data source remains ignorant of all our in-

memory changes until we tell the DevForce WinClient EntityManager to save the modified entity.

Nested Property Paths

We can traverse a business object graph by navigating along the path of its relations to adjacent business objects as

we learned in the section “Business Objects and Persistence.” The expression anEmployee.HomeAddress.City

begins with an employee object, emp, and follows its HomeAddress relation property to an address object before

invoking the City property of that address. The syntax is as graceful as anEmployee.FirstName which invokes a

property of the emp object proper. We refer generally to an expression such as anEmployee.HomeAddress.City

as a “nested property path” and to City as a “nested property.”

Formatting

Formatting is the process by which data is transformed on its way from a datasource to a UI control.

Datasource UI ControlFormatting

We introduced this topic in the last chapter. Let‟s revisit it again to understand the problem and learn how DevForce

WinClient solves it with data converters.

Formatting in Native .NET

In native .NET, some formatting can be done in the constructors for a System.Windows.Form.Binding. In the

statement below, the date formatting string “d” is passed into the Binding‟s constructor:

C#

Binding aBinding = new Binding("Value", mEmployeesBS, "BirthDate",

true, DataSourceUpdateMode.OnValidation, System.DateTime.Today, "d");

VB

Dim aBinding As New Binding("Value", mEmployeesBS, "BirthDate", _

True, DataSourceUpdateMode.OnValidation, System.DateTime.Today, "d")

The signature of that particular overload is as follows:

C# public Binding(string propertyName, object dataSource, string dataMember, bool formattingEnabled, System.Windows.Forms.DataSourceUpdateMode dataSourceUpdateMode, object nullValue, string formatString)

VB Public Sub New(ByVal propertyName As String, ByVal dataSource As Object, ByVal dataMember As String, ByVal formattingEnabled As Boolean, ByVal dataSourceUpdateMode As System.Windows.Forms.DataSourceUpdateMode, ByVal nullValue As Object, ByVal formatString As String)

Another overload adds a formatInfo parameter that takes a System.IFormatProvider:

Page 414: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

414 | P a g e

C# public Binding(string propertyName, object dataSource, string dataMember, bool formattingEnabled, System.Windows.Forms.DataSourceUpdateMode dataSourceUpdateMode, object nullValue, string formatString, System.IFormatProvider formatInfo)

VB Public Sub New(ByVal propertyName As String, ByVal dataSource As Object, ByVal dataMember As String, ByVal formattingEnabled As Boolean, ByVal dataSourceUpdateMode As System.Windows.Forms.DataSourceUpdateMode, ByVal nullValue As Object, ByVal formatString As String, ByVal formatInfo As System.IFormatProvider)

The IFormatProvider provides a mechanism for retrieving an object to control formatting.

Binding also provides a Format event that can be handled. The handler can perform just about any desired

transformation of the incoming data:

C#

public void SetBindings() {

Binding aBinding = new Binding("Text", mProductsBS, "UnitPrice");

aBinding.Format += new System.Windows.Forms.ConvertEventHandler(DecimalToCurrencyString);

mUnitPriceTextBox.DataBindings.Add(aBinding);

}

private void DecimalToCurrencyString(object sender, System.Windows.Forms.ConvertEventArgs

e) {

if (e.DesiredType == typeof(string)) {

e.Value = (System.Convert.ToDecimal(e.Value)).ToString("c");

}

}

VB Public Sub SetBindings ()

Dim aBinding As New Binding("Text", mProductsBS, "UnitPrice")

AddHandler aBinding.Format, AddressOf DecimalToCurrencyString

mUnitPriceTextBox.DataBindings.Add(aBinding)

End Sub

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

System.Windows.Forms.ConvertEventArgs)

If e.DesiredType Is GetType(String) Then

e.Value = (CDec(e.Value)).ToString("c")

End If

End Sub

Formatting with DevForce WinClient DataConverters

DevForce WinClient DataConverters provide several different options for performing required formatting on a

property value. At the most basic and straightforward end, converters for DateTime and Numeric types132

provide a

FormatString property that uses the formatting strings pre-defined in .NET. Table 17 shows the .NET formatting

strings for DateTime types; Table 18 shows the .NET formatting strings for numeric types. In addition to the

formatting strings shown in the table, you can define custom formatting strings for both DateTimes and numerics.

Table 17. Formatting Strings for DateTime Types (from the .NET Framework Help File)

Format

specifier

Name Description

132 DateConverter, DateRangeConverter, NumericConverter, and NumericRangeConverter

Page 415: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

415 | P a g e

d Short date

pattern

Displays a pattern defined by the DateTimeFormatInfo.ShortDatePattern property associated with the current

thread or by a specified format provider.

D Long date pattern

Displays a pattern defined by the DateTimeFormatInfo.LongDatePattern property associated with the current thread or by a specified format provider.

t Short time

pattern

Displays a pattern defined by the DateTimeFormatInfo.ShortTimePattern property associated with the current

thread or by a specified format provider.

T Long time

pattern

Displays a pattern defined by the DateTimeFormatInfo.LongTimePattern property associated with the current

thread or by a specified format provider.

f Full date/time pattern (short

time)

Displays a combination of the long date and short time patterns, separated by a space.

F Full date/time pattern (long

time)

Displays a pattern defined by the DateTimeFormatInfo.FullDateTimePattern property associated with the current thread or by a specified format provider.

g General

date/time

pattern (short

time)

Displays a combination of the short date and short time patterns, separated by a space.

G General

date/time

pattern (long time)

Displays a combination of the short date and long time patterns, separated by a space.

M or m Month day

pattern

Displays a pattern defined by the DateTimeFormatInfo.MonthDayPattern property associated with the current

thread or by a specified format provider.

R or r RFC1123

pattern

Displays a pattern defined by the DateTimeFormatInfo.RFC1123Pattern property associated with the current

thread or by a specified format provider. This is a defined standard and the property is read-only; therefore, it is

always the same regardless of the culture used, or the format provider supplied. The property references the CultureInfo.InvariantCulture property and follows the custom pattern "ddd, dd MMM yyyy HH:mm:ss G\MT".

Note that the 'M' in "GMT" needs an escape character so it is not interpreted. Formatting does not modify the

value of the DateTime; therefore, you must adjust the value to GMT before formatting.

s Sortable

date/time

pattern; conforms to

ISO 8601

Displays a pattern defined by the DateTimeFormatInfo.SortableDateTimePattern property associated with the

current thread or by a specified format provider. The property references the CultureInfo.InvariantCulture

property, and the format follows the custom pattern "yyyy-MM-ddTHH:mm:ss".

u Universal sortable

date/time

pattern

Displays a pattern defined by the DateTimeFormatInfo.UniversalSortableDateTimePattern property associated with the current thread or by a specified format provider. Because it is a defined standard and the property is

read-only, the pattern is always the same regardless of culture or format provider. Formatting follows the custom

pattern "yyyy-MM-dd HH:mm:ssZ". No time zone conversion is done when the date and time is formatted; therefore, convert a local date and time to universal time before using this format specifier.

U Universal

sortable date/time

pattern

Displays a pattern defined by the DateTimeFormatInfo.FullDateTimePattern property associated with the current

thread or by a specified format provider. The time displayed is the universal time, rather than the local time, equivalent to the DateTime value.

Y or y Year month pattern

Displays a pattern defined by the DateTimeFormatInfo.YearMonthPattern property associated with the current thread or by a specified format provider.

Any other

single character

Unknown

specifier

Table 18. Formatting Strings for Numeric Types (from the .NET Framework Help File)

Format Name Description

Page 416: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

416 | P a g e

specifier

C or c Currency The number is converted to a string that represents a currency amount. The conversion is controlled by the

currency format information of the NumberFormatInfo object used to format the number. The precision specifier

indicates the desired number of decimal places. If the precision specifier is omitted, the default currency precision given by the NumberFormatInfo is used.

D or d Decimal This format is supported for integral types only. The number is converted to a string of decimal digits (0-9),

prefixed by a minus sign if the number is negative. The precision specifier indicates the minimum number of digits desired in the resulting string. If required, the number is padded with zeros to its left to produce the number

of digits given by the precision specifier.

E or e Scientific (exponential)

The number is converted to a string of the form "-d.ddd…E+ddd" or "-d.ddd…e+ddd", where each 'd' indicates a digit (0-9). The string starts with a minus sign if the number is negative. One digit always precedes the decimal

point. The precision specifier indicates the desired number of digits after the decimal point. If the precision

specifier is omitted, a default of six digits after the decimal point is used. The case of the format specifier indicates whether to prefix the exponent with an 'E' or an 'e'. The exponent always consists of a plus or minus sign and a

minimum of three digits. The exponent is padded with zeros to meet this minimum, if required.

F or f Fixed-point The number is converted to a string of the form "-ddd.ddd…" where each 'd' indicates a digit (0-9). The string

starts with a minus sign if the number is negative. The precision specifier indicates the desired number of decimal

places. If the precision specifier is omitted, the default numeric precision given by the NumberFormatInfo is used.

G or g General The number is converted to the most compact of either fixed-point or scientific notation, depending on the type of the number and whether a precision specifier is present. If the precision specifier is omitted or zero, the type of the

number determines the default precision, as indicated by the following list.

Byte or SByte: 3 Int16 or UInt16: 5

Int32 or UInt32: 10

Int64 or UInt64: 19 Single: 7

Double: 15

Decimal: 29 Fixed-point notation is used if the exponent that would result from expressing the number in scientific notation is

greater than -5 and less than the precision specifier; otherwise, scientific notation is used. The result contains a

decimal point if required and trailing zeroes are omitted. If the precision specifier is present and the number of significant digits in the result exceeds the specified precision, then the excess trailing digits are removed by

rounding. If scientific notation is used, the exponent in the result is prefixed with 'E' if the format specifier is 'G',

or 'e' if the format specifier is 'g'. The exception to the preceding rule is if the number is a Decimal and the precision specifier is omitted. In that

case, fixed-point notation is always used and trailing zeroes are preserved.

N or n Number The number is converted to a string of the form "-d,ddd,ddd.ddd…", where each 'd' indicates a digit (0-9). The string starts with a minus sign if the number is negative. Thousand separators are inserted between each group of

three digits to the left of the decimal point. The precision specifier indicates the desired number of decimal places. If the precision specifier is omitted, the default numeric precision given by the NumberFormatInfo is used.

P or p Percent The number is converted to a string that represents a percent as defined by the

NumberFormatInfo.PercentNegativePattern property or the NumberFormatInfo.PercentPositivePattern property. If the number is negative, the string produced is defined by the PercentNegativePattern and starts with a minus

sign. The converted number is multiplied by 100 in order to be presented as a percentage. The precision specifier

indicates the desired number of decimal places. If the precision specifier is omitted, the default numeric precision given by NumberFormatInfo is used.

R or r Round-trip The round-trip specifier guarantees that a numeric value converted to a string will be parsed back into the same

numeric value. When a numeric value is formatted using this specifier, it is first tested using the general format, with 15 spaces of precision for a Double and 7 spaces of precision for a Single. If the value is successfully parsed

back to the same numeric value, it is formatted using the general format specifier. However, if the value is not

successfully parsed back to the same numeric value, then the value is formatted using 17 digits of precision for a

Double and 9 digits of precision for a Single. Although a precision specifier can be appended to the round-trip

format specifier, it is ignored. Round trips are given precedence over precision when using this specifier. This

format is supported by floating-point types only.

X or x Hexadecimal The number is converted to a string of hexadecimal digits. The case of the format specifier indicates whether to

use uppercase or lowercase characters for the hexadecimal digits greater than 9. For example, use 'X' to produce

"ABCDEF", and 'x' to produce "abcdef". The precision specifier indicates the minimum number of digits desired in the resulting string. If required, the number is padded with zeros to its left to produce the number of digits given

by the precision specifier. This format is supported for integral types only.

Page 417: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

417 | P a g e

For more complex formatting tasks, DataConverters have a FormatCore method that can be overridden in a subclass.

Parsing

Parsing -- the complement of formatting -- refers to the transformation of a data item as it travels from a UI control

to a datasource.

Datasource UI ControlParsing

Parsing in Native .NET

System.Windows.Forms.Binding provides a Parsing event that can be handled to control this process.

C#

public void SetBindings() {

Binding aBinding = new Binding("Text", mProductsBS, "UnitPrice");

aBinding.Parse += new System.Windows.Forms.ConvertEventHandler(CurrencyStringToDecimal);

mUnitPriceTextBox.DataBindings.Add(aBinding);

}

private void CurrencyStringToDecimal(

object sender, System.Windows.Forms.ConvertEventArgs e) {

if (e.DesiredType == typeof(decimal)) {

e.Value = decimal.Parse(e.Value.ToString());

}

}

VB Public Sub SetBindings ()

Dim aBinding As New Binding("Text", mProductsBS, "UnitPrice")

AddHandler aBinding.Parse, AddressOf CurrencyStringToDecimal

mUnitPriceTextBox.DataBindings.Add(aBinding)

End Sub

Private Sub CurrencyStringToDecimal(ByVal sender As Object, _

ByVal e As System.Windows.Forms.ConvertEventArgs)

If e.DesiredType Is GetType(Decimal) Then

e.Value = Decimal.Parse(e.Value.ToString())

End If

End Sub

Parsing with DevForce WinClient Data Converters

The RegexConverter, for string types, has a Regex property which takes a Regex string. This string imposes the

specified structure upon data being typed into the control. The same converter, and the TextConverter as well, has a

TrimWhitespace property which, if set to True, causes trailing spaces to be eliminated from the value as it passes to

the data source. Both the RegexConverter and TextConverter also have a ParseEmptyStringAsNull property which,

if set to True, converts an empty string to a Null.

For more complex parsing tasks, DataConverters have a ParseCore method that can be overridden in a subclass.

Page 418: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

418 | P a g e

Metadata

DevForce WinClient data binding framework is especially well-suited for binding to DevForce WinClient business

object as evidenced by its use of the metadata present in business objects.

For example, many business object properties map to database character columns with a maximum length. The

Object Mapper captures such maxima and inscribes them as attributes to the corresponding properties of DevForce

WinClient-generated business object classes.

The “FirstName” column of the IdeaBladeTutorial Employee table has a 30 character maximum. The Object

Mapper includes the MaxTextLength(30) attribute in code for the FirstName property in the

EmployeeDataRow class. The UI Binding framework can pick this up and automatically configure the UI control to

enforce the 30 char max. If the UI control does not support this feature, the UI Binding framework can still validate

the length and display a message to the user if the entry is too long.

Custom and Third-Party Controls

We are not limited to binding to .NET controls. Third parties offer a great number of controls. Many provide a better

user experience than their comparable .NET controls. Some are completely novel, offering innovative ways to

interact with a user.

DevForce WinClient data binding works with any control that has been “wrapped” in a descendent of the DevForce

WinClient DataBinder class. Control interfaces are remarkably inconsistent both within .NET and across vendors.

A DataBinder class adapts the idiosyncratic interface of a control to our standard data binding API.

DevForce WinClient ships with DataBinders for .NET controls and for control suites from several popular

vendors. The DevForce WinClient package includes a few well-wrapped custom controls of its own.

You can write your own controls and build DataBinders for them rather easily. They will fall right into place

among the other controls we can bind to manually. You can also add them to the list of controls offered by the

Designers with a few tweaks to a configuration file.

Writing a Designer is a different story. Of course it can be done. But Designers are highly specialized applications.

To write them requires deep understanding of Visual Studio and a lot of patience in debugging. This enterprise is

beyond the scope of current documentation.

Data Binding to Data Objects of Any Type

The BindingSource, introduced in .NET 2.0, greatly improves the developers‟ ability to bind to properties of any

object or list of objects. True bi-directional data binding is possible only when the data objects implement property

change notification. However, it is still useful to bind to data objects that do not provide such notification. Putting

them in a BindingSource makes that possible.

We tend to demonstrate data binding with business objects like employees and orders: no wonder, for business

objects are the predominant interest of our user community. However, it is often useful to bind to other kinds of

things and, in fact, we can bind to anything that exposes public properties such as:

Abstract classes

Interfaces

Web services

Forms and other controls

Structures

We should pause a moment to consider these possibilities.

Page 419: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

419 | P a g e

Binding to Abstract Classes

It is not unusual for a portion of a UI to display a collection of disparate but similar entities. For example, a produce

shipper might fill a grid with Vegetables, not caring in this context about the diversity of vegetable products he has

to sell.

There are final classes for each kind of vegetable, such as Potato, Onion, Carrot, etc. Each of them inherits from

the abstract base class, Vegetable, which defines their common properties such as Name, LotNumber, Quantity,

and Weight.

In our example we‟ll display vegetables in a .NET DataGridView in six easy steps:

Target a DataGridBindingViewManager for vegetables and aim it at a DataGridView on the form.

Add columns bound to Vegetable properties.

Create a DevForce WinClient BindableList to hold the vegetables. We need this to get good binding behavior

like nested and dynamic properties.

Fill the list with some vegetables from the database

Create a BindingSource and set its DataSource to our vegetable BindableList.

Assign the vegetable BindingSource to the DataGridBindingViewManager.

The UI Designers work with concrete classes only, not abstract or UI classes so we have to do this

programmatically.

We‟ll program this one in C# and the next one in VB:

// Step 1: Aim the DataGridViewManager (DGVM) at Vegetables and a grid. mVegetableDGVM.BoundType = typeof(Vegetable); // Binding to vegetables mVegetableDGVM.DataGridView = mVegetableDataGridView; // Attach to grid // Step 2: Add some Vegetable columns DataGridViewBindingDescriptorCollection dc = mVegetableDGVM.Descriptors; dc.Add("Vegetable", "Name"); // Add a column as {label}, {PropertyName} dc.Add("Lot", "LotNumber"); dc.Add("Qty", "Quantity"); dc.Add("Wt", "Weight"); // Step 3: Create BindableList<Vegetable> // to give our vegetable list good binding behavior. BindableList<Vegetable> bl = new BindableList<Vegetable>(); // Step 4: Fetch some vegetables and add „em to the list. DomainModelEntityManager em = DomainModelEntityManager.DefaultManager; bl.AddRange(em.Potatoes.ToList()); bl.AddRange(em.Onions.ToList()); bl.AddRange(em.Carrots.ToList());

// Step 5: Create BindingSource with our BindableList in it. mVegetableBindingSource = new BindingSource(); mVegetableBindingSource.DataSource = bl; // Step 6: Give the vegetables to the DataGridViewManager. mVegetableDGVM.BindingSource = mVegetableBindingSource;

The grid might look like this:

Page 420: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

420 | P a g e

Binding to Interfaces

We often encounter two very different stored representations of a similar thing. For example, we might present the

user with a cross-vendor catalog of products. The vendors have graciously given us access to their on-line catalogs.

Each has a notion of “product” but they are all so different that it seems pointless to have them share the same

abstract class.

For whatever reason, we decide that each vendor will have its own Product class and those classes will implement

the IProduct interface.

The steps the same as for an abstract class implementation; this time we write it in VB.

' Step 1: Aim the DataGridViewManager (DGVM) at IProducts and a grid mProductDGVM.BoundType = GetType(IProduct) ' Binding to product interface mProductDGVM.DataGridView = dataGridView1 ' Attach to grid ' Step 2: Add some IProduct columns Dim dc As DataGridViewBindingDescriptorCollection = mProductDGVM.Descriptors dc.Add("Vendor", "Vendor") ' Add a column as {label}, {PropertyName} dc.Add("Product Name", "Name") dc.Add("Serial #", "SerialNumber") dc.Add("Qty", "Quantity") ' Step 3: Create BindableList<IProduct> ' to give our product list good binding behavior. Dim bl As BindableList(Of IProduct) = New BindableList(Of IProduct)() ' Step 4: Fetch some products and add 'em to the list. Dim pm As PersistenceManager = PersistenceManager.DefaultManager bl.AddRange(pm.GetEntities(GetType(Product_VendorA))) bl.AddRange(pm.GetEntities(GetType(Product_VendorB))) ' Step 5: Create BindingSource with our BindableList in it. mIProductBindingSource = New BindingSource() mIProductBindingSource.DataSource = bl ' Step 6: Give the products to the DataGridViewManager. mProductDGVM.BindingSource = mIProductBindingSource

The IProduct grid might look like this:

Page 421: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

421 | P a g e

Binding to Web Services

Web service objects are great candidates for bi-directional data binding.

Imagine that our application reserves a corner of screen real estate to display a web service. Maybe we show the

weather forecast in the user‟s home zip code. Maybe we display up-to-the-minute stock quotes for the user‟s

portfolio. May we need shipping rates for products on the order we‟re taking right now.

If we turn the Web service into a DevForce WinClient business object, we can bind it bi-directionally just as we can

bind to an entity mapped to a database object.

We may choose to interact with the Web service directly, outside of the business object model. In this case we might

wrap the class returned by the Visual Studio web service wizard in another class (e.g., myWebService) so that we

can expose useful public properties (instead of public data members), raise PropertyChanged events, and provide

some insulation from service failures.

We drop instances of myWebService into a BindableList<myWebService> and make this the DataSource of

a BindingSource.

We can use the UI Designers if we wish because myWebService is a concrete class.

Binding to Controls as Data Objects

We are accustomed to thinking of business objects as the data objects in a data binding. We might easily forget that

controls themselves can be data objects.

Our form might have information that we should display in the UI. The form‟ public properties might expose the

name of the user, the step name in a workflow, and the parameters of a query filter. We can write

mFormBindingSource.DataSource = myForm

and display these properties in controls on the form itself for the user to see and, perhaps, change.

When to Use .NET Data Binding Instead

A UI widget‟s data property is its most important property but not the only one with a visible effect. Color, font

style, visibility, and location are just a few of the additional properties of a typical UI control.

We can bind to these properties with .NET data binding and it may be useful to do so. For example, we might want

to set TextBox text against a different background color when the value is out of the normal range.

In DevForce WinClient today we can only bind to the control‟s display property. Fortunately, non-data properties

rarely need formatting, parsing, or validation so many of the extended capabilities of DevForce WinClient data

binding are not needed. Regular .NET data binding works fine in most of these cases if we manage the object-to-

control binding carefully.

Page 422: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

422 | P a g e

When Not to Use Data Binding at All

Most UI widgets are on screen so that users may view and change data. Here‟s an example of ComboBox that sets

the employee‟s manager. If I pick “Buchanan, Steven”, Janet‟s manager will change from Andrew to Steven.

Sometimes a UI widget is on screen for other reasons, such as to navigate from one object to the next. Below left we

see Janet again. The ComboBox shows we are about to select “Nancy Davolio”. When we click, the form changes to

display Nancy‟s record, as we can see on the right. We‟ve just “navigated” from the “Janet” to the “Nancy”

employee.

In the first case, we used the ComboBox to change the value of an object and we used data binding to do it. In the

second, no values were touched; we used the ComboBox to move from one object to another but we didn‟t use data

binding to marry the control to the datasource. Same gadget, same business object; different purpose, different

technique.133

133 For both ComboBoxes, we provide a BindableList (or EntityList) as the DataSource, and specify the FullName property as

the ComboBox‟s DisplayMember. For the “Change the Manager” ComboBox, we bind the Employee‟s Manager property to

the ComboBox‟s ValueMember: but for the navigation-only ComboBox, we set no such data binding. In both cases, selecting

a new item moves the position pointer in the ComboBox‟s DataSource; for navigation purposes, that‟s all the effect we need.

Page 423: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

423 | P a g e

Both DevForce WinClient and native .NET data binding are intended to facilitate the exchange of data between a

control and an object property as in case #1 where the ComboBox changes the employee‟s Manager property.

Both DevForce WinClient and .NET data binding are the wrong tool when the control serves a different purpose as

in case #2, where it shifts from one employee object to another.

UI Architecture

A detailed discussion of UI design is outside the scope of this guide. It will suffice to note here that Model-View-

Controller architecture has gained, justifiably, a very good reputation. While few applications today attempt

rigorously to separate View and Controller (or Presenter) logic -- because the benefit gained from doing so is often

not worth the effort it takes or the complexity it introduces -- the separation of Model logic from View and

Controller logic is another matter. This separation contributes greatly toward making code easier to write,

understand, test, and maintain. It facilitates the consistent application of business rules, facilitates code re-use, and

provides many other benefits.

In a DevForce WinClient application, the Business Object Model discussed in earlier chapters is so fundamental

that the separation of Model from the UI view and control classes is practically automatic.

Nested Property Paths

We can traverse a business object graph by navigating along the path of its relations to adjacent business objects as

we learned in the section “Business Objects and Persistence.” The expression anEmployee.HomeAddress.City

begins with an employee object, emp, and follows its HomeAddress relation property to an address object before

invoking the City property of that address. The syntax is as graceful as anEmployee.FirstName which invokes a

property of the emp object proper. We refer generally to an expression such as anEmployee.HomeAddress.City

as a “nested property path” and to City as a “nested property.”

The BindableList(of T)

DevForce WinClient includes a class, IdeaBlade.Util.BindableList(Of T), that subclasses and extends .NET‟s

System.ComponentModel.BindingList(Of T) to support dynamic management of binding properties and the creation

of managed (or “live”) lists. The BindableList supports all of the list management features of the BindingList --

including methods such as InsertItem(), RemoveItem(), ClearItems(), ResetBindings(), etc., and the events

AddingNew and ListChanged – but adds additional support for manipulating the property list of contained items.

The latter feature forms the basis of DevForce WinClient‟s support both for nested property references using dot

notation (Order.Customer.LastName), and for runtime-only properties on business objects.

BindableList(Of T) also supports the attachment of ListManagers. These watch the PersistenceManager cache for

newly appearing items or newly modified items, adding them to the managed list if they meet specified criteria.

The BindableList(Of T) can contain items of any type, including interfaces and abstract types. A subclass of

BindableList(Of T), the EntityList(Of T), is designed specifically for working with DevForce WinClient business

(Entity) classes.134

In a WinForms application, you'll normally assign the BindableList (or one of its subclasses) to a

System.ComponentModel.BindingSource.DataSource, leaving the BindingSource.DataMember unassigned. When

constructing a IdeaBlade.UI.WinForms.BindingManager, you can assign the BindableList to the

BindingSource.DataSource, or place the BindableList in a BindingSource and then set the

IdeaBlade.UI.WinForms.BindingManager.BindingSource property.

The IdeaBlade.Util.BindableList(Of T).PropertyDescriptors for a BindableList contain the

System.ComponentModel.PropertyDescriptors appropriate for objects of the ItemType. By default, the list's

134 Another class, the ReadOnlyEntityList(Of T), acts as a wrapper around EntityList(Of T), preventing items from being

directly added or removed by a consumer.

Page 424: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

424 | P a g e

PropertyDescriptorList is one maintained globally by the IdeaBlade.Util.PropertyDescriptorList class. You may

choose to maintain a private PropertyDescriptorList instead by setting the list's IdeaBlade.Util.BindableList(Of

T).UsesGlobalPropertyDescriptors property to False. We discuss this in greater detail later in this chapter.

BindableList(of T) Versus System.ComponentModel.BindingList

As mentioned above, the DevForce WinClient BindableList subclasses System.ComponentModel.BindingList. But

exactly how does it improve upon it? It does so in several ways:

1. By supporting dynamic properties and altered collections of properties;

2. By providing enhanced facilities for sorting;

3. By supporting the use of ListManagers that can discover newly added or altered instances of the contained

type and add them to specified lists;

4. By fixing a memory leak.

Support for Dynamic Properties and Altered Collections of Properties

First of all, BindableList(Of T) implements two important interfaces that BindingList does not. One,

System.ComponentModel.ITypedList, is a .NET interface; the other, IdeaBlade.Util.IAdaptiveList, is a DevForce

WinClient interface. The first provides support for the dynamic management of PropertyDescriptors; that is, it

provides the foundation for altering the published schemas of items contained in the list. Suppose, for example, that

you have a Ball object with properties of Color, Material, and Diameter. But for some particular purpose you want

to expose a list of such Ball objects and have the contained objects appear not to have a Color property. For another

purpose you want a list of Ball objects that have not only the properties Color, Material, and Diameter, but also a

Bounceability property – even though Bounceability isn‟t a property that is defined in the Ball class itself. You

want the consumers of this second list to find a Bounceability property, and you‟re willing to take care of making

sure it reports a correct value. You want this special property to be a first-class citizen in every respect, including

being one to which a data binding can be set.

ITypedList provides the means to accomplish these objectives. It provides a GetItemProperties() method that can

return whatever collection of properties (or more precisely, PropertyDescriptors) you want consumers to see.

However, for this to work, a consumer must use this GetItemProperties() method to obtain the list of properties

(rather than using reflection against the contained objects). DevForce WinClient binding facilities always use

GetItemProperties() to determine what properties are available for binding, and this is the basis for DevForce

WinClient‟s support of deeply nested property references (e.g., anEmployee.Manager.Manager.LastName) and also

for dynamic properties.

ITypedList is a great start, but it would be of little use without a mechanism for actually altering the list of

PropertyDescriptors reported by a list. Left to its own devices, even the BindableList(Of T) will report only (and all

of) those PropertyDescriptors that it finds intrinsically associated with its contained type. (That is, for our Ball

object it will report Color, Material, and Diameter, the set of properties defined in the Ball class.) But how to add

and remove properties? That capability is provided by implementation of the IdeaBlade.Util.IAdaptiveList

interface.

IAdaptiveList provides three methods -- AddPropertyDescriptor(), GetPropertyDescriptor(), and

RemovePropertyDescriptor() – and a Boolean property, UsesGlobalPropertyDescriptors().

AddPropertyDescriptor() and RemovePropertyDescriptor(), as their names imply, permit PropertyDescriptors to be

added to and removed from the collection published by ITypedList.GetItemProperties(). GetPropertyDescriptor()

permits you to get the PropertyDescriptor associated with a given property path (like

“anEmployee.Manager.Manager.LastName”, the last name of an employee‟s boss‟s boss).

DevForce WinClient keeps a global dictionary of PropertiesDescriptors by entity type. You call

AddPropertyDescriptor() against a particular BindableList, but the PropertyDescriptor you add, under normal

circumstances, also gets added to the global dictionary, under the collection of PropertyDescriptors for the type

Page 425: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

425 | P a g e

contained in that particular BindableList. That means that the next BindableList you create to contain that type will

also report any properties you added or removed using AddPropertyDescriptor() or RemovePropertyDescriptor()

against some other list. This is usually the behavior you want: when you add a property dynamically, you want it to

be present in all collections of the type to which you added it. Nevertheless, there are occasions when that is not

what you want. Instead you want to manipulate the set of PropertyDescriptors exposed only by a particular

BindableList. For that circumstance, you can set the UsesGlobalPropertyDescriptors() property of the list to False

(away from its default of True). When you set UsesGlobalPropertyDescriptors() to False, the collection of

PropertyDescriptors reported for the contained type starts out being the same as the collection maintained in the

global PropertyDescriptors list; but subsequent adds and removes affect only the local BindableList, which now

maintains a separate copy of the PropertyDescriptors collection for the contained type, instead of obtaining that

collection, upon demand, from the global list.

Enhanced Facilities for Sorting

System.Component.BindingList provides a method, ApplySortCore(), which must be overridden by a derived class

to provide sort capability on the list. BindableList(Of T) does override this method, and in addition provides five

overloads of an ApplySort method that uses it. You can call ApplySort() with a string-valued property name, a

PropertyDescriptor, or a System.Collections.Generic.IComparer(Of T). (The latter permits complex sorts, including

multi-column sorts.) Three of the overloads support a pKeepListSorted parameter. By setting this to true, your list

stays sorted even as new items are added and existing items are changed.

Support for List Managers

The third pillar of BindableList(of T)‟s superiority over BindingList consists in its support for ListManagers. Each

BindableList(of T) has a ListManager property which, if defined, is an object instantiated from a class that

implements IdeaBlade.Util.IListManager. This ListManager watches some external environment – which might be

a file system, the DevForce WinClient cache, or anything else – for new instances of the type contained in the

BindableList. It checks these new instances – or existing instances whose state has changed – against specified

criteria. If the ListManager discovers an instance that meets the criteria, it adds that instance to the BindableList(Of

T) that it is managing. A given ListManager can manage multiple BindableLists(Of T), adding instances as needed

to all of them.

In DevForce WinClient, ListManagers are frequently used with EntityLists (which subclass BindableList and are

discussed in more detail later in this section) to watch the DevForce WinClient PersistenceManager cache. As new

business objects appear there that meet specified criteria, they are added to the managed EntityLists.

BindingList Memory Leak

The System.ComponentModel.BindingList shares a general problem related to containers which are consumers of

events published by their containees. Naturally, a BindingList, acting as a container, acquires strong references to

the objects it contains. But it also listens for a PropertyChanged event on contained objects which (like DevForce

WinClient business objects) implement the System.ComponentModel.INotifyPropertyChanged interface. In order

for the contained objects to be able to notify the containing lists when one of their property values changes, they

must themselves hold a strong reference to the containing list. In short, the list holds strong references to the

contained objects; and the contained objects each hold a strong reference to the list. It‟s like two wrestlers

desperately clutching each other by the throat.

The container can‟t be garbage-collected as long as anything else holds a reference to any of the objects it contains.

In the example below, a System.ComponentModel.BindingList, aList, is instantiated and populated with a few

Order objects. Then, inside a loop, aList is cloned a thousand times. Each clone, after being instantiated,

immediately goes out of scope. But the Order objects it contains each holds a strong reference back to it. And since

those Order objects remain alive in the original list aList, their strong references to the clone lists also remain alive,

preventing the clone lists from being garbage collected. The Assert statement succeeds invariably, indicating a

memory buildup in excess of 100,000 bytes attributable directly to the activities of the method.

Page 426: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

426 | P a g e

C#

public void BindingListMemoryLeak() {

// Show the leak in the BindingList (as opposed to our bindable list)

IList<Order> aList = FillOrderList(new BindingList<Order>(), true);

// Instantiate the CloneList logic so any effect that has

// on memory won‟t be counted

CloneList(aList);

long memStart = GC.GetTotalMemory(true);

for (int i = 1; i < 1000; i++) {

IList<Order> orders = CloneList(aList);

// Change a property value to raise PropertyChanged event.

// This will ensure that the PropertyChanged event handler

// is hooked up even if optimizations delay that until

// the handler is needed.

orders[2].Company = "test xxxxxx";

}

long memEnd = GC.GetTotalMemory(true);

long dif = memEnd - memStart;

Assert.IsTrue(dif > 100000);

}

private BindingList<Order> FillOrderList(BindingList<Order> pList,

bool pRegisterListChanged) {

if (pRegisterListChanged) {

pList.ListChanged += new ListChangedEventHandler(ListChangedHandler);

}

pList.Add(new Order("Test1", 1));

pList.Add(new Order("Test2", 2));

pList.Add(new Order("Test3", 3));

pList.Add(new Order("Test4", 4));

return pList;

}

VB

Public Sub BindingListMemoryLeak()

' Show the leak in the BindingList (as opposed to our bindable list)

Dim aList As IList(Of Order) = FillOrderList(New BindingList(Of Order)(), True)

' Instantiate the CloneList logic so any effect that has

' on memory won‟t be counted

CloneList(aList)

Dim memStart As Long = GC.GetTotalMemory(True)

For i As Integer = 1 To 999

Dim orders As IList(Of Order) = CloneList(aList)

' Change a property value to raise PropertyChanged event.

' This will ensure that the PropertyChanged event handler

' is hooked up even if optimizations delay that until

' the handler is needed.

orders(2).Company = "test xxxxxx"

Next i

Dim memEnd As Long = GC.GetTotalMemory(True)

Dim dif As Long = memEnd - memStart

Assert.IsTrue(dif > 100000)

End Sub

Private Function FillOrderList(ByVal pList As BindingList(Of Order), ByVal

pRegisterListChanged As Boolean) As BindingList(Of Order)

If pRegisterListChanged Then

AddHandler pList.ListChanged, AddressOf ListChangedHandler

End If

pList.Add(New Order("Test1", 1))

pList.Add(New Order("Test2", 2))

pList.Add(New Order("Test3", 3))

pList.Add(New Order("Test4", 4))

Return pList

End Function

DevForce WinClient‟s BindableList prevents this problem by some clever programming. Notification to the lists

about property value changes in their contained objects is accomplished by way of an intermediate object. The

Page 427: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

427 | P a g e

contained Order objects hold strong references to the intermediate object; but the intermediate object has only a

weak reference to the containing list.

ListContained

Object

Intermediary

Strong ReferenceWeak Reference

Because of that, the (IdeaBlade.Util.BindableList) clone lists are able to be garbage-collected even when other

objects continue to hold references to the objects they contain; and minimal memory buildup occurs.

Here‟s the corresponding test for the DevForce WinClient BindableList. It succeeds when captive memory does not

build up; and it succeeds reliably.

C#

public void BindableListMemoryLeak() {

IList<Order> aList = FillOrderList(new BindableList<Order>(), true);

CloneList(aList);

long memStart = GC.GetTotalMemory(true);

for (int i = 1; i < 1000; i++ ) {

IList<Order> orders = CloneList(aList);

orders[2].Company = "test xxxxxx";

}

long memEnd = GC.GetTotalMemory(true);

long dif = memEnd - memStart;

Assert.IsTrue(dif < 2000);

}

//FillOrderList is the same as that shown in the previous example

VB

Public Sub BindableListMemoryLeak()

Dim aList As IList(Of Order) = FillOrderList(New BindableList(Of Order)(), True)

CloneList(aList)

Dim memStart As Long = GC.GetTotalMemory(True)

For i As Integer = 1 To 999

Dim orders As IList(Of Order) = CloneList(aList)

orders(2).Company = "test xxxxxx"

Next i

Dim memEnd As Long = GC.GetTotalMemory(True)

Dim dif As Long = memEnd - memStart

Assert.IsTrue(dif < 2000)

End Sub

'FillOrderList is the same as that shown in the previous example

Sorting a BindableList(Of T)

The BindableList‟s ApplySort() method has several overloads. Let‟s examine them, in order, to learn more about

the sorting facilities of the BindableList.

Page 428: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

428 | P a g e

Figure 9. Overloads of BindableList(Of T).ApplySort()

Overload Action and Parameters 1 Public Sub ApplySort(ByVal pPropertyName As

String, ByVal pDirection As System.ComponentModel.ListSortDirection, ByVal pKeepListSorted As Boolean) Member of: IdeaBlade.Util.BindableList(Of T)

Sorts the list based on the property name and direction specified. Parameters: pPropertyName: Name of the property on which to sort pDirection: The sort direction

pKeepListSorted: Whether to keep the list sorted

2 Public Sub ApplySort(ByVal pProperty As System.ComponentModel.PropertyDescriptor, ByVal pDirection As System.ComponentModel.ListSortDirection, ByVal pKeepListSorted As Boolean)

Member of: IdeaBlade.Util.BindableList(Of T)

Sorts the list based on the System.ComponentModel.PropertyDescriptor and direction specified. Parameters:

pProperty: PropertyDescriptor on which to sort pDirection: The sort direction pKeepListSorted: Whether to keep the list sorted

3 Public Sub ApplySort(ByVal pProperty As System.ComponentModel.PropertyDescriptor, ByVal pDirection As System.ComponentModel.ListSortDirection) Member of: IdeaBlade.Util.BindableList(Of T)

Sorts the list based on the System.ComponentModel.PropertyDescriptor and direction specified. Parameters: pProperty: PropertyDescriptor on which to sort

pDirection: The sort direction

4 Public Sub ApplySort(ByVal pDirection As System.ComponentModel.ListSortDirection) Member of: IdeaBlade.Util.BindableList(Of T)

Sorts the list based on the natural comparator for the IdeaBlade.Util.BindableList(Of T).ItemType. Parameters:

pDirection: The sort direction

5 Public Sub ApplySort(ByVal pComparer As System.Collections.Generic.IComparer(Of T), ByVal pKeepListSorted As Boolean) Member of: IdeaBlade.Util.BindableList(Of T)

Sorts the elements in the list using the specified comparer. Parameters: pComparer: The IComparer implementation to use when comparing elements.

pKeepListSorted: Whether to keep the list sorted

The first-listed overload takes a string-valued property name, a direction, and a boolean indicator of whether you

want the sort order to be maintained as new items are added to the list, or as contained items change:

C#

public void ApplySort(string pPropertyName, System.ComponentModel.ListSortDirection

pDirection, bool pKeepListSorted)

VB Public Sub ApplySort(ByVal pPropertyName As String, ByVal pDirection As

System.ComponentModel.ListSortDirection, ByVal pKeepListSorted As Boolean)

The second-listed overload takes essentially the same pieces of information, except that you specify the sort property

using a PropertyDescriptor rather than a string-valued property name:

Page 429: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

429 | P a g e

C#

public void ApplySort(System.ComponentModel.PropertyDescriptor pProperty,

System.ComponentModel.ListSortDirection pDirection, bool pKeepListSorted)

VB Public Sub ApplySort(ByVal pProperty As System.ComponentModel.PropertyDescriptor, ByVal pDirection

As System.ComponentModel.ListSortDirection, ByVal pKeepListSorted As Boolean)

The third-listed overload is like the second, but omits the parameter specifying whether you want the sort order

maintained (and performs only a one-time sort):

C# public void ApplySort(System.ComponentModel.PropertyDescriptor pProperty,

System.ComponentModel.ListSortDirection pDirection)

VB Public Sub ApplySort(ByVal pProperty As System.ComponentModel.PropertyDescriptor, ByVal pDirection

As System.ComponentModel.ListSortDirection)

The fourth-listed overload requires only the sort direction, and sorts on the contained type‟s “natural comparator”.

A type has a “natural comparator” if it implements the System.IComparable interface. DevForce WinClient business

entities, by default, do not; but simple types like strings and integers do, and you could certainly choose to

implement the interface on one of your business classes. Like the third-listed overload, this one performs only a

one-time sort.

C# public void ApplySort(System.ComponentModel.ListSortDirection pDirection)

VB Public Sub ApplySort(ByVal pDirection As System.ComponentModel.ListSortDirection)

If you use this overload, be sure the contained type implements IComparable! If it does not, your results may be

strange and unusual.

The last-listed overload of ApplySort(), like #4, uses a System.Collections.Generic.IComparer(Of T); except here

you specify it, instead of using one that‟s built into the contained type.

C#

public void ApplySort(System.Collections.Generic.IComparer<T> pComparer,

bool pKeepListSorted)

VB

Public Sub ApplySort( _

ByVal pComparer As System.Collections.Generic.IComparer(Of T), _

ByVal pKeepListSorted As Boolean)

This overload is far and away the most flexible and powerful of the five. Let‟s begin looking at it by implementing

a hard-coded ascending sort of Employees on LastName, FirstName.

C#

public class EmployeeLastNameFirstNameAscendingComparer : IComparer<Employee> {

public int Compare(Model.Employee x, Model.Employee y) {

int retVal = 0;

if (x.IsNullEntity & y.IsNullEntity) {

//Both are null; don't change their order

retVal = 0;

}

else if (x.IsNullEntity & ! y.IsNullEntity) {

Page 430: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

430 | P a g e

//x is null and y is not; make x first

retVal = -1;

}

else if (! x.IsNullEntity & y.IsNullEntity) {

//y is null and x is not; make y first

retVal = 1;

}

else {

//Neither is null; do the comparison.

//Start by comparing the LastNames.

retVal = string.Compare(x.LastName, y.LastName, true,

System.Globalization.CultureInfo.CurrentCulture);

//Note that we‟ve used an overload of String.Compare that

//takes a CurrentCulture parameter. This will ensure that

//our sort works even our app is moved to a different

//country and language, where the rules for sorting may

//be different.

if (retVal == 0) {

//Same LastName; let the FirstName determine the ordering.

retVal = string.Compare(x.FirstName, y.FirstName, true,

System.Globalization.CultureInfo.CurrentCulture);

}

}

return retVal;

}

}

VB

Public Class EmployeeLastNameFirstNameAscendingComparer

Implements IComparer(Of Employee)

Public Function Compare(ByVal x As Model.Employee, ByVal y As Model.Employee) As Integer _

Implements System.Collections.Generic.IComparer(Of Model.Employee).Compare

Dim retVal As Integer

If x.IsNullEntity And y.IsNullEntity Then

'Both are null; don't change their order

retVal = 0

ElseIf x.IsNullEntity And Not y.IsNullEntity Then

'x is null and y is not; make x first

retVal = -1

ElseIf Not x.IsNullEntity And y.IsNullEntity Then

'y is null and x is not; make y first

retVal = 1

Else

'Neither is null; do the comparison.

'Start by comparing the LastNames.

retVal = String.Compare(x.LastName, y.LastName, True, _

System.Globalization.CultureInfo.CurrentCulture)

'Note that we‟ve used an overload of String.Compare that

'takes a CurrentCulture parameter. This will ensure that

'our sort works even our app is moved to a different

'country and language, where the rules for sorting may

'be different.

If retVal = 0 Then

Page 431: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

431 | P a g e

'Same LastName; let the FirstName determine the ordering.

retVal = String.Compare(x.FirstName, y.FirstName, True, _

System.Globalization.CultureInfo.CurrentCulture)

End If

End If

Return retVal

End Function

End Class

We could use the above IComparer to sort a BindableList of Employee objects in a statement like the following:

C# mEmployees.ApplySort(new LastNameFirstNameComparer(), true);

VB mEmployees.ApplySort(New LastNameFirstNameComparer(), True)

Fair enough, but perhaps you see a problem: We could end up writing a lot of such IComparers, and would have to

anticipate every desired sort. So let‟s cut to the chase and look at a very general IComparer. This one works on any

business object (Entity) type; will sort on any number of properties; and allows for specifying the sort direction

desired on each included property. Now that’s an IComparer you can really ride into town on!

Page 432: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

432 | P a g e

C#

using IdeaBlade.Persistence;

using IdeaBlade.Util;

/// <summary>

/// Arbitrary n-column sort

/// </summary>

/// <remarks>This version works with all properties: simple, calculated, nested.</remarks>

public class MultiPropertyComparer<T> : IComparer<T> where T : Entity

public MultiPropertyComparer(BindableList<SortProperty<T>> pSortProperties) {

mSortProperties = pSortProperties;

}

public int Compare(T x, T y) {

int retVal = 0;

if (x.IsNullEntity & y.IsNullEntity) {

//Both are null; don't change their order

retVal = 0;

}

else if (x.IsNullEntity & ! y.IsNullEntity) {

//x is null and y is not; make x first

retVal = -1;

}

else if (! x.IsNullEntity & y.IsNullEntity) {

//y is null and x is not; make y first

retVal = 1;

}

else {

//Neither is null; do the comparison

foreach (SortProperty<T> aSortProperty in mSortProperties) {

IdeaBlade.Util.PropertyComparer<T> comparer = new

IdeaBlade.Util.PropertyComparer<T>(aSortProperty.Descriptor, aSortProperty.Direction);

retVal = comparer.Compare(x, y);

if (retVal != 0) {

break;

}

}

}

return retVal;

}

#region Private Fields

private BindableList<SortProperty<T>> mSortProperties;

#endregion

}

VB

Imports IdeaBlade.Persistence

Imports IdeaBlade.Util

''' <summary>

''' Arbitrary n-column sort

''' </summary>

''' <remarks>This version works with all properties: simple, calculated, nested.</remarks>

Public Class MultiPropertyComparer(Of T As Entity)

Implements IComparer(Of T)

Public Sub New(ByVal pSortProperties As BindableList(Of SortProperty(Of T)))

mSortProperties = pSortProperties

End Sub

Public Function Compare(ByVal x As T, ByVal y As T) As Integer _

Implements System.Collections.Generic.IComparer(Of T).Compare

Dim retVal As Integer

If x.IsNullEntity And y.IsNullEntity Then

Page 433: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

433 | P a g e

'Both are null; don't change their order

retVal = 0

ElseIf x.IsNullEntity And Not y.IsNullEntity Then

'x is null and y is not; make x first

retVal = -1

ElseIf Not x.IsNullEntity And y.IsNullEntity Then

'y is null and x is not; make y first

retVal = 1

Else

'Neither is null; do the comparison

For Each aSortProperty As SortProperty(Of T) In mSortProperties

Dim comparer As New IdeaBlade.Util.PropertyComparer(Of T) _

(aSortProperty.Descriptor, aSortProperty.Direction)

retVal = comparer.Compare(x, y)

If retVal <> 0 Then

Exit For

End If

Next

End If

Return retVal

End Function

#region "Private Fields"

Private mSortProperties As BindableList(Of SortProperty(Of T))

#end Region

End Class

Here‟s the SortItem class used by our MultiPropertyComparer:

C#

using System.ComponentModel;

using IdeaBlade.Persistence;

using IdeaBlade.Util.PropertyDescriptorFns;

public class SortItem<t> where t: entity {

public SortItem(string pPropertyPath, ListSortDirection pDirection) {

mDirection = pDirection;

mDescriptor = GetPropertyDescriptor(typeof(t), pPropertyPath);

}

public SortItem(PropertyDescriptor pDescriptor, ListSortDirection pDirection) {

mDescriptor = pDescriptor;

mDirection = pDirection;

}

public PropertyDescriptor Descriptor {

get {

return mDescriptor;

}

set {

mDescriptor = value;

}

}

public ListSortDirection Direction {

get {

return mDirection;

}

set {

mDirection = value;

}

}

#region Private Fields

private PropertyDescriptor mDescriptor;

private ListSortDirection mDirection;

#endregion

Page 434: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

434 | P a g e

}

VB

Imports System.ComponentModel

Imports IdeaBlade.Persistence

Imports IdeaBlade.Util.PropertyDescriptorFns

Public Class SortItem(Of t As entity)

Public Sub New(ByVal pPropertyPath As String, ByVal pDirection As ListSortDirection)

mDirection = pDirection

mDescriptor = GetPropertyDescriptor(GetType(t), pPropertyPath)

End Sub

Public Sub New(ByVal pDescriptor As PropertyDescriptor, ByVal pDirection As

ListSortDirection)

mDescriptor = pDescriptor

mDirection = pDirection

End Sub

Public Property Descriptor() As PropertyDescriptor

Get

Return mDescriptor

End Get

Set(ByVal value As PropertyDescriptor)

mDescriptor = value

End Set

End Property

Public Property Direction() As ListSortDirection

Get

Return mDirection

End Get

Set(ByVal value As ListSortDirection)

mDirection = value

End Set

End Property

#region "Private Fields"

Private mDescriptor As PropertyDescriptor

Private mDirection As ListSortDirection

#end Region

End Class

Here are a few sample usages of the MultiPropertyComparer:

1. Sort Employees ascendingly by LastName, FirstName, passing the sort properties as string-valued names:

C#

BindableList<SortItem<Employee>> SortItems = new BindableList<SortItem<Employee>()>();

SortItems.Add(new SortItem<Employee>("LastName", ListSortDirection.Ascending));

SortItems.Add(new SortItem<Employee>("FirstName", ListSortDirection.Ascending));

mEmployees.ApplySort(new MultiPropertyComparer<Employee>(SortItems), true);

VB

Dim SortItems As New BindableList(Of SortItem(Of Employee))

SortItems.Add(New SortItem(Of Employee)("LastName", ListSortDirection.Ascending))

SortItems.Add(New SortItem(Of Employee)("FirstName", ListSortDirection.Ascending))

mEmployees.ApplySort(New MultiPropertyComparer(Of Employee)(SortItems), True)

2. Sort Employees ascendingly by LastName, FirstName, passing the sort properties as PropertyDescriptors:

Page 435: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

435 | P a g e

C#

BindableList<SortItem<Employee>> SortItems = new BindableList<SortItem<Employee>()>();

SortItems.Add(new SortItem<Employee>(EntityPropertyDescriptors.Employee.LastName,

ListSortDirection.Ascending));

SortItems.Add(new SortItem<Employee>(EntityPropertyDescriptors.Employee.FirstName,

ListSortDirection.Ascending));

mEmployees.ApplySort(new MultiPropertyComparer<Employee>(SortItems), true);

VB

Dim SortItems As New BindableList(Of SortItem(Of Employee))

SortItems.Add(New SortItem(Of Employee)( _

EntityPropertyDescriptors.Employee.LastName, ListSortDirection.Ascending))

SortItems.Add(New SortItem(Of Employee)( _

EntityPropertyDescriptors.Employee.FirstName, ListSortDirection.Ascending))

mEmployees.ApplySort(New MultiPropertyComparer(Of Employee)(SortItems), True)

3. Sort Employees descendingly by the count of Orders on which they acted as SalesRep; then ascendingly by

their TotalOrderRevenue:135

C#

BindableList<SortItem<Employee>> SortItems =

new BindableList<SortItem<Employee>()>();

SortItems.Add(new SortItem<Employee>

(PropertyDescriptorFns.GetPropertyDescriptor(typeof(Employee), "Orders.Count"),

ListSortDirection.Descending));

SortItems.Add(new SortItem<Employee>

(PropertyDescriptorFns.GetPropertyDescriptor(typeof(Employee), "TotalOrderRevenue"),

ListSortDirection.Ascending));

mEmployees.ApplySort(new MultiPropertyComparer<Employee>(SortItems), true);

VB

Dim SortItems As New BindableList(Of SortItem(Of Employee))

SortItems.Add(New SortItem(Of Employee) _

(PropertyDescriptorFns.GetPropertyDescriptor( _

GetType(Employee), "Orders.Count"), ListSortDirection.Descending))

SortItems.Add(New SortItem(Of Employee) _

(PropertyDescriptorFns.GetPropertyDescriptor( _

GetType(Employee), "TotalOrderRevenue"), ListSortDirection.Ascending))

mEmployees.ApplySort(New MultiPropertyComparer(Of Employee)(SortItems), True)

4. Sort Products ascendingly by the CompanyName of the product Suppler, then descendingly by the

UnitsInStock, UnitsOnOrder, and ReorderLevels:

135 We have used the GetPropertyDescriptor() method in this case to get the necessary PropertyDescriptors, rather than looking

for them in the EntityPropertyDescriptors collection, because neither Orders.Count nor TotalOrderRevenue can be found in

the latter. TotalOrderRevenue isn‟t there because it‟s a custom property, and the Object Mapper, keeping the clean

separation between the developer- and DataRow-level classes, only generates EntityPropertyDescriptors for properties that

are defined in the latter. The developer can manually add EntityPropertyDescriptors to the developer-level class, but the

above code sample does not assume this has been done.

The case of Orders.Count is different. Like TotalOrderRevenue, it has no EntityPropertyDescriptor in the EmployeeDataRow

class; nor is there one in the Order class, since Count is not a property of Order, but of the ICollection (list) in which the

Employee‟s Orders reside. GetPropertyDescriptor() buffers both kinds of issue, returning a PropertyDescriptor that will do

the desired job.

Page 436: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

436 | P a g e

C#

BindableList<SortItem<Product>> SortItems =

new BindableList<SortItem<Product>()>();

SortItems.Add(new SortItem<Product>

("Supplier.CompanyName", ListSortDirection.Ascending));

SortItems.Add(new SortItem<Product>("UnitsInStock", ListSortDirection.Descending));

SortItems.Add(new SortItem<Product>("UnitsOnOrder", ListSortDirection.Descending));

SortItems.Add(new SortItem<Product>("ReorderLevel", ListSortDirection.Descending));

mProducts.ApplySort(new MultiPropertyComparer<Product>(SortItems), true);

VB

Dim SortItems As New BindableList(Of SortItem(Of Product))

SortItems.Add(New SortItem(Of Product)("Supplier.CompanyName", _

ListSortDirection.Ascending))

SortItems.Add(New SortItem(Of Product)("UnitsInStock", _

ListSortDirection.Descending))

SortItems.Add(New SortItem(Of Product)("UnitsOnOrder", _

ListSortDirection.Descending))

SortItems.Add(New SortItem(Of Product)("ReorderLevel", _

ListSortDirection.Descending))

mProducts.ApplySort(New MultiPropertyComparer(Of Product)(SortItems), True)

Manipulating the List of PropertyDescriptors for a BindableList(of T)

DevForce WinClient keeps a global list of System.ComponentModel.PropertyDescriptors for each business entity.

You could list the PropertyDescriptors for an Order entity with the following code:

C# IdeaBlade.Util.PropertyDescriptorList pdList =;

IdeaBlade.Util.PropertyDescriptorList.Get(typeof(Order));

foreach (System.ComponentModel.PropertyDescriptor descriptor in pdList) {

Console.WriteLine(descriptor.Name);

}

VB

Dim pdList As IdeaBlade.Util.PropertyDescriptorList =

IdeaBlade.Util.PropertyDescriptorList.Get(GetType(Order))

For Each descriptor As System.ComponentModel.PropertyDescriptor In pdList

Console.WriteLine(descriptor.Name)

Next

When binding objects in a BindableList, .NET consults this list to determine what properties are available for

binding on the objects it contains.136

Furthermore, DevForce WinClient provides functions to manipulate this list:

PropertyDescriptors can be added to it and removed from it. This not only facilitates DevForce WinClient‟s support

for binding to nested properties (e.g., Order.Customer.CompanyName); it also makes it possible to define new

properties at runtime, and then to bind objects in the UI to them. Thus, properties that serve only a UI function only,

and which therefore do not belong in the business model, need not be defined where they do not belong.

Suppose, for example, that on your Orders form you wish to highlight the FreightCost when it exceeds a specified

threshold for a given order. Specifically, you want to do this by changing the BackColor and ForeColor properties

of the control displaying FreightCost to a color combination that will cause the control to stand out.

You could define FreightCostBackColor and FreightCostForeColor properties on your Order business object; but

such properties are completely specific to the user interface. With DevForce WinClient, instead of doing that, you

can define the desired properties at runtime, assigning them to your Order business object. You can then bind the

136 This happens because the BindableList supports the .NET interface ITypedList, which is respected by the .NET WinForm

binding facilities (but not by the .NET WebForm facilities!).

Page 437: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

437 | P a g e

BackColor and ForeColor properties of the TextBox displaying the FreightCost to these dynamically defined

properties.

Here is the code to define and bind the BackColor property. (The code for the ForeColor property is similar.)

C# … mOrders.AddPropertyDescriptor("FreightCostBackColor", typeof(Order),

FreightCostBackColorGetter, null);

… private object FreightCostBackColorGetter(object pObject) {

Order currentOrder = (Order)pObject;

if (System.Convert.ToDecimal(currentOrder.FreightCost) >= 100) {

return Color.DarkRed;

}

else {

return System.Drawing.SystemColors.Window;

}

}

VB … mOrders.AddPropertyDescriptor("FreightCostBackColor", _

GetType(Order), AddressOf FreightCostBackColorGetter, Nothing)

… Private Function FreightCostBackColorGetter(ByVal pObject As Object) As Object

Dim currentOrder As Order = CType(pObject, Order)

If CType(currentOrder.FreightCost, Decimal) >= 100 Then

Return Color.DarkRed

Else

Return System.Drawing.SystemColors.Window

End If

End Function

Notice that the AddPropertyDescriptor() method of the mOrders EntityList (or BindableList) requires a Getter

delegate that is called whenever a value is needed for the new property. In this case we check to see if the

FreightCost is $100 or more. If so, we return the color DarkRed137

; otherwise, we return the standard BackColor

value for a TextBox control. We can then use .NET databinding to bind the BackColor property of a

FreightCostTextBox to the FreightCostBackColor property of our Order object.

C#

this.mFreightCostTextBox.DataBindings.Add("BackColor", mOrdersBS, "FreightCostBackColor");

VB

Me.mFreightCostTextBox.DataBindings.Add("BackColor", mOrdersBS, "FreightCostBackColor")

There is another way we could accomplish the same result: by subclassing a DevForce WinClient DataConverter,

teaching it to watch for high values, and writing a custom DevForce WinClient DataBinder to use the information.

We‟ll explore that in a later section of this chapter.

Global v. Private PropertyDescriptorList

As previously mentioned, DevForce WinClient maintains a global list of PropertyDescriptors for entity types. This

is initialized at application startup to include all those properties that are defined in the classes for the various entity

types (Employee, Order, Customer, etc.). However, DevForce WinClient also allows the developer to modify this

list, and provides methods to do so. The IdeaBlade.Util.PropertyDescriptorFns.BuildPropertyDescriptor() method,

137 We‟ve hard-coded the color for this example to make what we‟re doing more concrete; but in actual practice, this should be

avoided due to the problems it can create for color-blind users. It would be better to defer to another color property in the

System.Drawing.SystemColors collection, as we have done for the default color in the example.

Page 438: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

438 | P a g e

for example, permits a new property descriptor to be added to the list; the AddPropertyDescriptor() method on the

BindableList(Of T) does the same.

When a new PropertyDescriptor is added to the global list (by either method), the property described then “appears

to exist” on objects of the specified type. But it only does so when certain protocols are followed both by the list

containing the type and by other objects that use the list. The first must implement a System.ComponentModel

interface named ITypedList; the second must either use the GetItemProperties() method specified by that interface,

or use the PropertyDescriptors collection property of the BindableList.138

The good news is that not only DevForce

WinClient, but more and more third party control and library vendors, support the use of the ITypedList interface.

For this reason, you may find (for example) that your report writer (assuming it is a modern, object-friendly one)

may recognize your object‟s nested and dynamic properties without any special “training”.

BindableList(Of T) includes a boolean property, UsesGlobalPropertyDescriptors, that determines whether objects

that reference it should use the global list or a private list (BindableList.PropertyDescriptors).

UsesGlobalPropertyDescriptors defaults to True. When a developer sets this property to False, the BindableList

makes a private copy of the global PropertyDescriptorsList for its contained type. That private list can then be

manipulated (by adding, removing, or changing PropertyDescriptors) without affecting the behavior of other lists

that contain the same type.

Regardless of the setting of UsesGlobalPropertyDescriptors, the appropriate list of descriptors is available through

the BindableList‟s PropertyDescriptors property. If the UsesGlobalPropertyDescriptors property is set to true,

PropertyDescriptors returns the global PropertyDescriptorList associated with list; otherwise, it returns the list's

private PropertyDescriptorList.

ITypedList specifies the implementation of a GetItemProperties() method to “return the

System.ComponentModel.PropertyDescriptorCollection that represents the properties on each item used to bind

data”. Any class or object that uses GetItemProperties() to determine what properties are supported on the objects in

a given BindingList will get the correct list.

Binding to Entities in a BindableList(of T) Using a DevForce WinClient

BindingManager

When binding to DevForce WinClient Entities (business classes), you typically will collect your objects in an

EntityList(Of T).139

EntityList(Of T) subclasses BindableList(Of T) and adds a few methods and properties

specifically useful when working with Entities.

138 There are at least two approaches to obtaining the PropertyDescriptors collection for a BindableList that don’t work properly.

Neither approach is used within DevForce WinClient, of course, but either might be used by a third-party component. One

would be simply to use reflection directly against the type contained in the list. The other would be to use

GetItemProperties() incompletely, not bothering to recurse the PropertyDescriptors found. Either approach might produce a

list of PropertyDescriptors that is missing some or all of the nested or dynamic properties.

In some cases, calling AddPropertyDescriptor() on the BindableList from inside the code executed by the third-party

component (e.g., in the Page_Load handler of a report class) will get the component to recognize a complex property it would

otherwise not see.

139 DevForce WinClient itself uses a wrapper class, ReadOnlyEntityList(Of T), for nested property collections (such as

Employee.Orders). Although the consumer of a ReadOnlyEntityList can edit the objects it contains, it cannot add or remove

items to or from such a list by direct means (i.e., Add() or Remove()). However, DevForce WinClient always attaches an

EntityListManager to the ReadOnlyEntityLists it uses when generating code for nested property collections. Because of the

attached ListManager, any items newly appearing in the PersistenceManager cache (whether because of being fetched from a

datasource, imported from a different PersistenceManager, restored from an locally persisted EntitySet, or newly created)

will automatically be added to the nested property collections if they meet the collection‟s critieria. The same will be true of

pre-existing objects that are modified in such a way as to make them meet the collection‟s criteria, when previously they did

not.

You can declare and instantiate ReadOnlyEntityLists for your own use, with or without EntityListManagers, whenever you

want to provide an EntityList that cannot be grown or shrunk.

Page 439: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

439 | P a g e

With the combination of DevForce WinClient Entities stored in an EntityList() feeding a DevForce WinClient

BindingManager, you get the full capabilities of DevForce WinClient databinding: bi-directional binding, nested

properties, dynamic properties, insulation from valueless idiosyncracies of the object models of supported third-

party controls, and bindings collected together in a BindingManager‟s Descriptors collection for easy reference and

maintenance.

Binding to Non-Entities in a BindableList(of T) Using a DevForce WinClient

BindingManager

DevForce WinClient BindingManagers require BindableList(Of T) data sources. However, the BindableList(Of T) –

unlike its subclass, the EntityList(Of T) – does not require the objects it contained to be DevForce WinClient

entities. They can, in fact, be objects of any type. By collecting non-entity objects in a BindableList(Of T),

therefore, you can enable their participation in DevForce WinClient data binding. You will then enjoy the benefits

of having all your bindings to that type collected conveniently in a single location: the BindingManager‟s

Descriptors collection. You will be able to use nested property references, add dynamic properties, and even

implement special types of ListManagers.

Be aware, however, that you may not automatically get all the features associated with DevForce WinClient

databinding if you bind to non-entity classes. Specifically, the bi-directional databinding behavior that you see

when binding to DevForce WinClient Entities -- change the value of property in the business object: watch its

display value in the UI change automatically – depends upon the Entity‟s implementation of the

INotifyPropertyChanged interface. If you write your own (non-Entity) business class and implement this interface,

you can get the bi-directional binding behavior. If you bind to some other type that doesn‟t implement it, you won‟t.

When Not to Use a BindableList(of T)

The BindableList(Of T) carries overhead associated with its data binding and eventing behaviors. If you‟re not

going to databind and don‟t need a ListManager, but still want a list to host objects of a single type, use the .NET

System.Collections.Generic.List(Of T). If you‟re not going to databind, don‟t need a ListManager, and need a

collection that can accommodate items of more than one type, use System.Collections.ArrayList.

EntityPropertyDescriptors

EntityPropertyDescriptors are useful in databinding associated with Winform user interfaces. Their generation is

optional in DevForce WinClient Object Mapper, and should be turned off (as unnecessary) if you are not using

Winforms in your UI.

All data binding is a process of mapping from UI widgets to the properties of a data object. How do we identify the

data object properties to bind? .NET requires us to provide the name of the property as a string.

DevForce WinClient followed suit. Moreover DevForce WinClient extends .NET data binding so that we bind not

only to simple properties of the data object but also to properties of objects related to the data object.

For example, we can easily bind to an Employee‟s home state name by specifying the nested property path to the

state name as a string, e.g., “HomeAddress.State.Name”.

There are two salient problems with this approach:

We can easily mis-type the property path, or remember it incorrectly.

We may change the property name in the future, invalidating the string property path.

The compiler can‟t catch our mistake in either case. IntelliSense can‟t help us at all. Test cases might reveal the

problem but test coverage of UI is notoriously poor. Most likely we‟ll discover the problem during manual testing or

rely upon the poor customer to tell us that the application crashed.

Page 440: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

440 | P a g e

This is not a small, manageable risk. A decent sized object model may have hundreds of bound properties. No one

can check them all. No one can remember them all.

EntityPropertyDescriptors to the rescue.

EntityPropertyDescriptors are strongly-typed objects that serve both as .NET PropertyDescriptors and

as a means to identify property paths for data binding.

The expression, EntityPropertyDescriptors.Employee.HomeAddress.State.Name returns a DevForce

WinClient AdaptedPropertyDescriptor for the property that returns the value of an employee‟s home state name.

EntityPropertyDescriptors.Employee.HomeAddress.State.Name.PropertyPath yields the nested

property path string we need to perform data binding, “HomeAddress.State.Name”.

“Ridiculous!” you say. The expression is about twice as long as the string we need for binding. You continue, “I‟m

all for strong-typing but not at just any cost”. Well it‟s not as bad as it first seems. First, a simple code snippet can

be a big help; you can add one name “epd”, for example; then you can type “epd” followed by a tab and get

“EntityPropertyDescriptors” with the cursor positioned at the end, ready for you to enter a period.

Second, IntelliSense helps us walk the object graph.

This is a huge timesaver. If you‟re like us, you rarely know the property names well enough to be certain of what

they are or how they are spelled. Property specification in a case-sensitive language like C# is particularly error

prone without the assistance of IntelliSense. We have found that we can enter a property path more than three times

as fast with EntityPropertyDescriptors than we can by typing the string alone.

You get speed, confidence, and compile-time type-safety in one fell swoop!

Where do EntityPropertyDescriptors properties come from?

The Object Mapper creates an EntityPropertyDescriptors property for each of the business object properties

it generates.

Open the EmployeeDataRow class

Scroll to the region at the bottom

Open that region.

You‟ll see each of the Employee “simple properties” represented with an EntityPropertyDescriptors

property such as

C# /// <summary>Gets the … AdaptedPropertyDescriptor … for LastName.</summary>

public AdaptedPropertyDescriptor LastName {

get { return Get("LastName"); }

}

EXTEND THE ENTITYPROPERTYDESCRIPTORS CLASS

It‟s great that the Object Mapper creates these EntityPropertyDescriptors properties for the generated entity

properties. What about the custom properties we add to the final class?

You should be able to type EntityPropertyDescriptors.Employee.Age or

EntityPropertyDescriptors.Employee.Age just as you can enter

EntityPropertyDescriptors.Employee.LastName.

Page 441: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

441 | P a g e

And you can!

Open the Employee class

Scroll to the region at the bottom

Open that region.

You‟ll see a template for extending the EntityPropertyDescriptors class with properties that correspond to

your custom Employee properties. You can uncomment that template and start extending.

C#

public static partial class EntityPropertyDescriptors {

public partial class EmployeePropertyDescriptor : AdaptedPropertyDescriptor {

/// <summary>Gets the … AdaptedPropertyDescriptor … for Age.</summary>

public AdaptedPropertyDescriptor Age {

get { return this.Get("Age"); }

}

}

The EntityPropertyDescriptors is a class with inner classes

There is something curious about this EntityPropertyDescriptors class.

First, it‟s a static class (Shared in VB.Net). That alone is not strange but when we look at its public methods we

see only static properties returning singleton instances of other classes.

Second, these other classes are all defined inside the EntityPropertyDescriptors class itself and therefore

visible and accessible only if prefixed by “EntityPropertyDescriptors”. Thus, to get at an

EmployeePropertyDescriptor, we must write “EntityPropertyDescriptors.EmployeePropertyDescriptor”.

Third, there are as many of these inner classes as there are business object entities.

Fourth, each of these classes is defined in the DataRow class of its corresponding business object entity class.

Fifth, each of these classes is a “partial class” (as is EntityPropertyDescriptors itself).

Evidently, IdeaBlade expects us to extend each inner class in a file other than the one where the class was defined.

That is precisely what we do when we extend the EmployeePropertyDescriptor class by adding a property

corresponding to the Employee custom Age property. We code our property in the Employee class file rather than

the EmployeeDataRow class file where the Object Mapper put the other EmployeePropertyDescriptor

properties.

This should not be a surprise. The Object Mapper owns the DataRow class and we developers are forbidden to

change anything in this class file on pain of losing our work. We‟re supposed to enter our custom code in the “final”

Page 442: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

442 | P a g e

class. Only by defining the EmployeePropertyDescriptor as a partial class can we define it in both the

DataRow and the “final” Employee class files.

Partial classes were added to .NET in version 2.0.

Finally, most properties in these inner classes return an AdaptedPropertyDescriptor . That is a class you‟ve

probably never seen before although it‟s been in DevForce WinClient since 2001.

What is an AdaptedPropertyDescriptor?

An AdaptedPropertyDescriptor is an object that both describes and gives access to an object property.

The Employee class has a LastName property which means you can write expressions at design time like

anEmployee.LastName and anEmployee.LastName = “Smith” to get and set the property.

Sometimes your code needs to talk about that LastName property. You can talk about it with an

AdaptedPropertyDescriptor object. Actually, you can do more than talk about it. You can pass that object

around and use it anywhere to both get and set any Employee instance‟s LastName.

This ability to treat an object property as an object in its own right is extremely valuable to framework developers.

It‟s at the heart of how data binding works.

An AdaptedPropertyDescriptor is a DevForce WinClient construct that derives from the .NET

PropertyDescriptor abstract class. It augments the PropertyDescriptor class with, among other things, a

PropertyPath property that returns the string path which extends from the root object to the end point of the

descriptor.

Name versus PropertyPath

Do not confuse the AdaptedPropertyDescriptor.Name with the AdaptedPropertyDescriptor.PropertyPath.

The Name is the name of the property. The PropertyPath is the string representation of the path to that property.

The two are the same for simple properties of a business object.

EntityPropertyDescriptors.Employee.LastName.Name returns “LastName” and so does

EntityPropertyDescriptors.Employee.LastName.PropertyPath .

However, they differ markedly for nested properties:

EntityPropertyDescriptors.Employee.Manager.LastName.Name returns “__Manager_LastName”

EntityPropertyDescriptors.Employee.Manager.LastName.PropertyPath returns “Manager.LastName”.

“__Manager_LastName” is the name of a “dynamic property”. It‟s the name of a property created by

DevForce WinClient at runtime so that a UI control can be data bound to a value of a property on an object

related to the bound data object. The bound object in this case is the current employee object; call her

“Nancy Davolio”. Her related object is her manager, “Andrew”. His last name is “Fuller”.

The “LastName” property returns “Davolio”; the “__Manager_LastName” property returns “Fuller”.

Why DevForce WinClient creates dynamic properties and how it does so is beyond the scope of this topic. I

wouldn‟t even have broached the subject except that you were bound to confuse the “Name” with the

“PropertyPath” and discover the difference.

NEW METHODS OF ADAPTEDPROPERTYDESCRIPTOR

We added two new methods to the AdaptedPropertyDescriptor so that an AdaptedPropertyDescriptor

can discover or create child AdaptedPropertyDescriptors.

Page 443: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

443 | P a g e

C#

public AdaptedPropertyDescriptor Get(string pPropertyName)

public T Get<T>(String pPropertyName) where T : AdaptedPropertyDescriptor

You put these methods to work when you add a custom property to one of the EntityPropertyDescriptors

inner classes.

UI Designers

DevForce WinClient provides a number of UI designers, mostly for BindingManagers. A ControlBindingManager

designer facilitates visual selection and autopopulation of loose controls (from third-party control suites as well as

native .NET); grid-oriented BindingManagers perform a similar function for the .NET 2.0 DataGridView, the .NET

1.1 DataGrid, the Developer Express XtraGrid, and the Infragistics Ultragrid.

DevForce WinClient also provides a NullableDatePicker control that corrects certain deficiences in the .NET

DateTimePicker.

BindingManagerDesigners

The DevForce WinClient BindingManager designers permit easy selection of business object properties for data

binding; automatic (but overridable) selection of appropriate UI controls for those properties; basic configuration of

the controls through DataConverter settings; and round-trippable autopopulation of forms and UserControls with the

selected controls.

The ControlBindingManager handles loose controls (TextBoxes, DatePickers, ComboBoxes, and the like) for both

native .NET controls and those from Developer Express and Infragistics. If you have more than one of these control

suites available on your machine, all appropriate controls from the installed suites will be available for selection.

However, the ControlBindingManager will make its default choices from one of the suites which you have

designated as the preferred suite.

Grid controls are complex and require their own dedicated BindingManager. DevForce WinClient provides these

for the grids already enumerated. Working with the grid designers is an experience very similar to that of working

with the ControlBindingManager.

A primary design goal for the DevForce WinClient BindingManagers and their designers was to provide as

homogeneous an experience as possible across different control types and control suites. You will find that working

in the ControlBindingManager with controls from third-party control suites is very similar to working with similar

native .NET controls; that working with the DataGridViewBindingManager is very similar to working with the

ControlBindingManager; and that working with grid BindingManagers for third-party grids is very similar to

working with the DataGridViewBindingManager for the .NET grid control, the DataGridView. We have attempted

to insulate you, as much as possible, from the innumerable, often trivial but nonetheless draining and time-

consuming, differences in the available UI widgets. That way, you can keep your focus on providing needing

functionality for your end users!

Working with the ControlBindingManager

To begin the process of working the a ControlBindingManager, select that control from the IdeaBlade DevForce

WinClient panel in the Toolbox, drag the control onto the design surface of a form or UserControl, and drop it.

Page 444: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

444 | P a g e

A visual representation of the control will appear in

the Component Tray, with the name

ControlBindingManager1 if it is the first such

ControlBindingManager that you have dropped on

your form. Clicking on the Smart Tag on the

control will produce a menu like that shown at right:

Generally your first step will be to Autopopulate Controls. Clicking that option will produce a dialog like the

following, which asked “Bind to which object type?”

Page 445: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

445 | P a g e

Select the type whose properties you wish to bind to UI controls, and click OK. You‟ll see a dialog similar to the

following:

Page 446: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

446 | P a g e

You should see your business model assembly in the upper window; if you don‟t, make sure you have generated it

with the DevForce WinClient object mapper, compiled it, and added a reference to it in your user interface project!

Page 447: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

447 | P a g e

Note that custom properties (like Age), and relation

properties (like Manager, DirectReports, and

Orders) are all displayed. Note also that many

properties are displayed at expandable nodes,

indicating that drill-down is available. In the

following picture, we‟ve expanded a few of those

expandable nodes. The Address property, being

string-valued, exposes a Length property to which a

binding can be set. BirthDate, a DateTime property,

can be expanded multiple levels; if you like, you can

find to the Hours part of the TimeOfDay represented

in the date!

Page 448: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

448 | P a g e

If you expand the Manager property, which derives

from a recursive relationship on the underlying

Employee table140

, you see all the Employee

properties repeated, one level down – since the

Manager is yet another Employee. You can drill in

as far as you like, binding to the employee‟s

manager‟s manager‟s manager‟s manager‟s

LastName if you like.

To construct a binding to one of the properties in the tree, you simply drag the property into the window in the

Autopopulation tab, where it will become a row in a grid. In the picture below, we‟ve dragged the simple properties

LastName, FirstName, BirthDate, and Photo into the grid, along with the custom property age, the nested property

Manager.Photo, the relation property Manager, and the Count property of the Orders list. For each, the

BindingManager designer identified the data type, selected an appropriate Control, applied an appropriate control

name, and assigned an appropriate DataConverter.

If you click the checkbox labelled “Show All Properties”, more properties will appear in the tree. Now you‟ll see all

of the public properties defined for your Employee object: Column descriptors, the RdbEntity property

140 Each Employee record has a ReportsToEmployeeId column that contains the primary key for some other Employee record,

indicating a reporting relationship.

Page 449: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

449 | P a g e

ForceSqlDistinct, row properties like HasErrors and IsDetached, Entity properties like IsDeserializing and

IsNullEntity, and others. It‟s unusual to bind to these properties, but if you have the use case, we‟ve got the time.

Suppose we drag an additional property,

LastFirst, into the AutoPopulation grid; but

this time we inspect the ComboBox that

appears in the ControlType column. The

designer has chosen a TextBox for this

property; but it gives us the opportunity to

override that selection with other controls

from the .NET suite that might be suitable

candidates for a string-valued property.

Now please note the Preferences

button at the bottom of the Configure

Databindings dialog. If you click that,

you‟ll see the Control Suite dialog

shown at right. Here you indicate

which, if any, of the third-party control

suites supported by DevForce

WinClient whose controls you want

available for selection.

Page 450: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

450 | P a g e

At left we‟ve indicated we want controls from the Developer Express

control suite, version 6.1.3, to be available for selection. We‟re also

about to indicate that we want the same control suite to be the

preferred one, from which default assignments will be made. Having

done that, if we now drag a string-valued property into the

Autopopulation grid, see what happens:

Now the default choice is a DevExpress TextEdit control, and the selection list includes candidate controls from the

DevExpress suite as well as the .NET suite.

You can select as many supported control suites as you have installed. If DevForce WinClient isn‟t able to find the

indicated control suite on your machine, it will complain when you check the box.

Binding to Relation Properties

If you are very observant you may have noticed that the control type assigned to the Manager property that we

dragged over from the property tree was a ComboBox. A Manager is a complex object in its own right that we can‟t

display directly. We can display a photo of the Manager, or her LastName, or the like, but not the Manager object

itself.

Furthermore, as a one-to-one relation property, an Employee‟s Manager must be one from a finite list. It‟s a known

list, too: the set of Employees.141

DevForce WinClient has figured out that a ComboBox will provide a very

convenient medium for changing the value of the Employee‟s Manager property: the form will display a list of

potential Managers (or more accurately, a specific property of those potential Managers), and selecting one from the

ComboBox will change the Manager assigned to the Employee.

There‟s still a bit of configuration to do before that will work, however. The ComboBox needs a DataSource – a list

of Employees who can act as Managers – and it needs to know what property of those potential managers to display

in its dropdown list. We can supply these pieces of information by configuring the DataConverter assigned to the

ComboBox.

141 In this case, the Employee table relates recursively to itself, via the ReportsToEmployeeId foreign key; so the candidate

Managers also reside in the Employee table. But we would have a similar situation, so far as use of the

ControlBindingManager is concerned, if looking, let‟s say, at the Customer property of an Order object. Again there is a one-

to-one relation, though in that case the objects pointed to by the foreign key column happen to reside in a different table (the

more common situation).

Page 451: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

451 | P a g e

DevForce WinClient selected the ListConverter subclass of DataConverter for the ComboBox. If you click on the

button in the rightmost column of the Manager property row, you‟ll see the following dialog:

We need to set the DisplayMember property, and

provide a ListSource. Clicking in the

DisplayMember value column reveals a dropdown

list of properties from the entity type to be

represented in the ComboBox; in this case, an

Employee. We simply select the desired display

property from the list.

For List Source, if there is an appropriate candidate list – specifically, a .NET BindingSource – available, we can

select it from another dropdown. For those times when there is no appropriate BindingSource available, the Create

List button is provided. Clicking that button will create a BindingSource, dropping it in the Component Tray for the

form or UserControl we‟re working on. We‟ll have to remember to populate it with business objects in our code.

In .NET, when configuring a ComboBox, you normally specify values for three properties of the control:

1. the DataSource, which needs a collection of objects to display;

2. a DisplayMember, which is the name of a property on those objects, to expose for inspection in the

dropdown list; and

3. a ValueMember, which is the name of a property to which the control will be bound.

But note, in the ListConverter for the Manager property, that the ValueMember property of the ComboBox is set to

some property named “__Self”, and that it is disabled. Since we‟re configuring a ComboBox to permit the end user

to specify a particular Employee as Manager, the thing we want to return is an Employee object – not the Id of an

employee object, or some such. “__Self” is a special property that DevForce WinClient creates dynamically on

business entities. It represents, and returns, the host object itself. The bottom line is: you can leave the

ValueMember alone. It‟s already correct.

Page 452: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

452 | P a g e

Autopopulating the Container

Once you‟ve configured everything as desired, you can click the Ok button to autopopulate your form or

UserControl. The BindingManager will drop controls into the container, with appropriate accompanying labels, and

you have but to move them about a bit to get the layout you want.

Round-Tripping with the ControlBindingManager

It‟s not a one-way street. You can return to the ControlBindingManager designer to tweak the configurations and to

add or remove properties.

Remember the Smart Tag menu associated with the

ControlBindingManager‟s icon in the Component

Tray? Previously we chose the Autopopulate

Controls option. But what does that Configure

Databindings option do?

As it so happens, all it does is to display the same Configure Databinding dialog as did the Autopopulate option –

except that it selects the Existing Bindings tab instead of the Autopopulate tab. There‟s not a great deal of

difference between what you see on the two tabs. But on the Existing Bindings tab you can create a binding to an

existing control, as opposed to causing a new control to be generated. Because of that, dragging a property from the

property tree on to the Existing Bindings grid won‟t result in the assignment of a control or a DataConverter.

Page 453: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

453 | P a g e

Code Generated by the BindingManagerDesigner

The ControlBindingManager designer is integrated with Visual Studio, and as such must play by specific rules laid

out by Microsoft for Visual Studio designers. Chief among the requirements is that it any code it generates must

land in the designer code file associated with the form or UserControl (e.g., the EmployeeForm.Designer.cs or

EmployeeForm.Designer.vb file associated with an EmployeeForm.cs or EmployeeForm.vb file). There are strict

rules governing just where the code generated there must be placed. Object instantiations come first, followed by

BeginInit() calls, followed by control configuration code, followed by commands to add the instantiated controls to

the container, followed by EndInit() calls, followed by variable declarations.

It is well to get at least somewhat familiar with the layout of things in the designer code file, as the time may come

when you wish to move some of the generated code out of there and bring it under your explicit control, and the

code in the designer file can be instructive about how DevForce WinClient databindings are configured in code.

Here are code statements generated by the ControlBindingManager as configured in the preceding material, by

section:

Page 454: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

454 | P a g e

C# Object Instantiations Dim ListConverter1 As IdeaBlade.UI.ListConverter = _

New IdeaBlade.UI.ListConverter(GetType(Model.Employee), _

IdeaBlade.UI.Editability.[Optional])

Me.mFetchDataForOfflineWorkCheckBox = New System.Windows.Forms.CheckBox

Me.ControlBindingManager1 = New IdeaBlade.UI.WinForms.ControlBindingManager

Me.mLastNameLabel = New System.Windows.Forms.Label

Me.mLastNameTextBox = New System.Windows.Forms.TextBox

Me.mFirstNameLabel = New System.Windows.Forms.Label

Me.mFirstNameTextBox = New System.Windows.Forms.TextBox

Me.mBirthDateLabel = New System.Windows.Forms.Label

Me.mBirthDateDateTimePicker = New System.Windows.Forms.DateTimePicker

Me.mAgeLabel = New System.Windows.Forms.Label

Me.mAgeTextBox = New System.Windows.Forms.TextBox

Me.mOrdersCountLabel = New System.Windows.Forms.Label

Me.mOrdersCountTextBox = New System.Windows.Forms.TextBox

Me.mPhotoLabel = New System.Windows.Forms.Label

Me.mPhotoPictureBox = New System.Windows.Forms.PictureBox

Me.mManagerPhotoLabel = New System.Windows.Forms.Label

Me.mManagerPhotoPictureBox = New System.Windows.Forms.PictureBox

Me.mManagerLabel = New System.Windows.Forms.Label

Me.mManagerComboBox = New System.Windows.Forms.ComboBox

Me.mManagersBS = New System.Windows.Forms.BindingSource(Me.components)

BeginInit() Calls ((System.ComponentModel.ISupportInitialize)this.mEmployeesBindingNavigator).BeginInit();

this.mEmployeesBindingNavigator.SuspendLayout();

((System.ComponentModel.ISupportInitialize)this.ControlBindingManager1).BeginInit();

((System.ComponentModel.ISupportInitialize)this.mPhotoPictureBox).BeginInit();

((System.ComponentModel.ISupportInitialize)this.mManagerPhotoPictureBox).BeginInit();

((System.ComponentModel.ISupportInitialize)this.mManagersBS).BeginInit();

Control Configuration Code //

//ControlBindingManager1

//

this.ControlBindingManager1.BoundType = typeof(Model.Employee);

ListConverter1.DisplayMember = "LastFirst";

ListConverter1.ListSource = this.mManagersBS;

this.ControlBindingManager1.Descriptors.Add(new

IdeaBlade.UI.WinForms.ControlBindingDescriptor(this.mLastNameTextBox,

typeof(Model.Employee), "LastName"));

this.ControlBindingManager1.Descriptors.Add(new

IdeaBlade.UI.WinForms.ControlBindingDescriptor(this.mFirstNameTextBox,

typeof(Model.Employee), "FirstName"));

this.ControlBindingManager1.Descriptors.Add(new

IdeaBlade.UI.WinForms.ControlBindingDescriptor(this.mBirthDateDateTimePicker,

typeof(Model.Employee), "BirthDate"));

this.ControlBindingManager1.Descriptors.Add(new

IdeaBlade.UI.WinForms.ControlBindingDescriptor(this.mAgeTextBox, typeof(Model.Employee),

"Age"));

this.ControlBindingManager1.Descriptors.Add(new

IdeaBlade.UI.WinForms.ControlBindingDescriptor(this.mOrdersCountTextBox,

typeof(Model.Employee), "Orders.Count"));

this.ControlBindingManager1.Descriptors.Add(new

IdeaBlade.UI.WinForms.ControlBindingDescriptor(this.mPhotoPictureBox,

typeof(Model.Employee), "Photo"));

this.ControlBindingManager1.Descriptors.Add(new

IdeaBlade.UI.WinForms.ControlBindingDescriptor(this.mManagerPhotoPictureBox,

typeof(Model.Employee), "Manager.Photo"));

this.ControlBindingManager1.Descriptors.Add(new

IdeaBlade.UI.WinForms.ControlBindingDescriptor(this.mManagerComboBox,

typeof(Model.Employee), "Manager", ListConverter1));

//

//mLastNameLabel

//

this.mLastNameLabel.Location = new System.Drawing.Point(8, 32);

this.mLastNameLabel.Name = "mLastNameLabel";

this.mLastNameLabel.Size = new System.Drawing.Size(100, 23);

this.mLastNameLabel.TabIndex = 10;

Page 455: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

455 | P a g e

this.mLastNameLabel.Text = "Last Name";

this.mLastNameLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;

//

//mLastNameTextBox

//

this.mLastNameTextBox.Location = new System.Drawing.Point(112, 32);

this.mLastNameTextBox.Name = "mLastNameTextBox";

this.mLastNameTextBox.Size = new System.Drawing.Size(150, 20);

this.mLastNameTextBox.TabIndex = 11;

<Many more omitted>

Commands to Add the Instantiated Controls to the Container this.Controls.Add(this.mManagerComboBox);

this.Controls.Add(this.mManagerLabel);

this.Controls.Add(this.mManagerPhotoPictureBox);

this.Controls.Add(this.mManagerPhotoLabel);

this.Controls.Add(this.mPhotoPictureBox);

this.Controls.Add(this.mPhotoLabel);

this.Controls.Add(this.mOrdersCountTextBox);

this.Controls.Add(this.mOrdersCountLabel);

this.Controls.Add(this.mAgeTextBox);

this.Controls.Add(this.mAgeLabel);

this.Controls.Add(this.mBirthDateDateTimePicker);

this.Controls.Add(this.mBirthDateLabel);

this.Controls.Add(this.mFirstNameTextBox);

this.Controls.Add(this.mFirstNameLabel);

this.Controls.Add(this.mLastNameTextBox);

this.Controls.Add(this.mLastNameLabel);

EndInit() Calls

CType(Me.mEmployeesBindingNavigator, System.ComponentModel.ISupportInitialize).EndInit()

Me.mEmployeesBindingNavigator.ResumeLayout(False)

Me.mEmployeesBindingNavigator.PerformLayout()

CType(Me.ControlBindingManager1, System.ComponentModel.ISupportInitialize).EndInit()

CType(Me.mPhotoPictureBox, System.ComponentModel.ISupportInitialize).EndInit()

CType(Me.mManagerPhotoPictureBox, System.ComponentModel.ISupportInitialize).EndInit()

CType(Me.mManagersBS, System.ComponentModel.ISupportInitialize).EndInit()

Variable Declarations internal IdeaBlade.UI.WinForms.ControlBindingManager ControlBindingManager1;

internal System.Windows.Forms.TextBox mLastNameTextBox;

internal System.Windows.Forms.TextBox mFirstNameTextBox;

internal System.Windows.Forms.DateTimePicker mBirthDateDateTimePicker;

internal System.Windows.Forms.TextBox mAgeTextBox;

internal System.Windows.Forms.TextBox mOrdersCountTextBox;

internal System.Windows.Forms.PictureBox mPhotoPictureBox;

internal System.Windows.Forms.PictureBox mManagerPhotoPictureBox;

internal System.Windows.Forms.ComboBox mManagerComboBox;

internal System.Windows.Forms.Label mLastNameLabel;

internal System.Windows.Forms.Label mFirstNameLabel;

internal System.Windows.Forms.Label mBirthDateLabel;

internal System.Windows.Forms.Label mAgeLabel;

internal System.Windows.Forms.Label mOrdersCountLabel;

internal System.Windows.Forms.Label mPhotoLabel;

internal System.Windows.Forms.Label mManagerPhotoLabel;

internal System.Windows.Forms.Label mManagerLabel;

internal System.Windows.Forms.BindingSource mManagersBS;

Page 456: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

456 | P a g e

VB Object Instantiations Dim ListConverter1 As IdeaBlade.UI.ListConverter = _

New IdeaBlade.UI.ListConverter(GetType(Model.Employee), _

IdeaBlade.UI.Editability.[Optional])

Me.mFetchDataForOfflineWorkCheckBox = New System.Windows.Forms.CheckBox

Me.ControlBindingManager1 = New IdeaBlade.UI.WinForms.ControlBindingManager

Me.mLastNameLabel = New System.Windows.Forms.Label

Me.mLastNameTextBox = New System.Windows.Forms.TextBox

Me.mFirstNameLabel = New System.Windows.Forms.Label

Me.mFirstNameTextBox = New System.Windows.Forms.TextBox

Me.mBirthDateLabel = New System.Windows.Forms.Label

Me.mBirthDateDateTimePicker = New System.Windows.Forms.DateTimePicker

Me.mAgeLabel = New System.Windows.Forms.Label

Me.mAgeTextBox = New System.Windows.Forms.TextBox

Me.mOrdersCountLabel = New System.Windows.Forms.Label

Me.mOrdersCountTextBox = New System.Windows.Forms.TextBox

Me.mPhotoLabel = New System.Windows.Forms.Label

Me.mPhotoPictureBox = New System.Windows.Forms.PictureBox

Me.mManagerPhotoLabel = New System.Windows.Forms.Label

Me.mManagerPhotoPictureBox = New System.Windows.Forms.PictureBox

Me.mManagerLabel = New System.Windows.Forms.Label

Me.mManagerComboBox = New System.Windows.Forms.ComboBox

Me.mManagersBS = New System.Windows.Forms.BindingSource(Me.components)

BeginInit() Calls CType(Me.mEmployeesBindingNavigator,

System.ComponentModel.ISupportInitialize).BeginInit()

Me.mEmployeesBindingNavigator.SuspendLayout()

CType(Me.ControlBindingManager1, System.ComponentModel.ISupportInitialize).BeginInit()

CType(Me.mPhotoPictureBox, System.ComponentModel.ISupportInitialize).BeginInit()

CType(Me.mManagerPhotoPictureBox, System.ComponentModel.ISupportInitialize).BeginInit()

CType(Me.mManagersBS, System.ComponentModel.ISupportInitialize).BeginInit()

Control Configuration Code '

'ControlBindingManager1

'

Me.ControlBindingManager1.BoundType = GetType(Model.Employee)

ListConverter1.DisplayMember = "LastFirst"

ListConverter1.ListSource = Me.mManagersBS

Me.ControlBindingManager1.Descriptors.Add( _

New IdeaBlade.UI.WinForms.ControlBindingDescriptor( _

Me.mLastNameTextBox, GetType(Model.Employee), "LastName"))

Me.ControlBindingManager1.Descriptors.Add( _

New IdeaBlade.UI.WinForms.ControlBindingDescriptor( _

Me.mFirstNameTextBox, GetType(Model.Employee), "FirstName"))

Me.ControlBindingManager1.Descriptors.Add( _

New IdeaBlade.UI.WinForms.ControlBindingDescriptor( _

Me.mBirthDateDateTimePicker, GetType(Model.Employee), "BirthDate"))

Me.ControlBindingManager1.Descriptors.Add( _

New IdeaBlade.UI.WinForms.ControlBindingDescriptor( _

Me.mAgeTextBox, GetType(Model.Employee), "Age"))

Me.ControlBindingManager1.Descriptors.Add( _

New IdeaBlade.UI.WinForms.ControlBindingDescriptor( _

Me.mOrdersCountTextBox, GetType(Model.Employee), "Orders.Count"))

Me.ControlBindingManager1.Descriptors.Add( _

New IdeaBlade.UI.WinForms.ControlBindingDescriptor( _

Me.mPhotoPictureBox, GetType(Model.Employee), "Photo"))

Me.ControlBindingManager1.Descriptors.Add( _

New IdeaBlade.UI.WinForms.ControlBindingDescriptor( _

Me.mManagerPhotoPictureBox, GetType(Model.Employee), "Manager.Photo"))

Me.ControlBindingManager1.Descriptors.Add( _

New IdeaBlade.UI.WinForms.ControlBindingDescriptor( _

Me.mManagerComboBox, GetType(Model.Employee), "Manager", ListConverter1))

'

'mLastNameLabel

'

Me.mLastNameLabel.Location = New System.Drawing.Point(8, 32)

Me.mLastNameLabel.Name = "mLastNameLabel"

Me.mLastNameLabel.Size = New System.Drawing.Size(100, 23)

Page 457: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

457 | P a g e

Me.mLastNameLabel.TabIndex = 10

Me.mLastNameLabel.Text = "Last Name"

Me.mLastNameLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft

'

'mLastNameTextBox

'

Me.mLastNameTextBox.Location = New System.Drawing.Point(112, 32)

Me.mLastNameTextBox.Name = "mLastNameTextBox"

Me.mLastNameTextBox.Size = New System.Drawing.Size(150, 20)

Me.mLastNameTextBox.TabIndex = 11

<Many more omitted>

Commands to Add the Instantiated Controls to the Container Me.Controls.Add(Me.mManagerComboBox)

Me.Controls.Add(Me.mManagerLabel)

Me.Controls.Add(Me.mManagerPhotoPictureBox)

Me.Controls.Add(Me.mManagerPhotoLabel)

Me.Controls.Add(Me.mPhotoPictureBox)

Me.Controls.Add(Me.mPhotoLabel)

Me.Controls.Add(Me.mOrdersCountTextBox)

Me.Controls.Add(Me.mOrdersCountLabel)

Me.Controls.Add(Me.mAgeTextBox)

Me.Controls.Add(Me.mAgeLabel)

Me.Controls.Add(Me.mBirthDateDateTimePicker)

Me.Controls.Add(Me.mBirthDateLabel)

Me.Controls.Add(Me.mFirstNameTextBox)

Me.Controls.Add(Me.mFirstNameLabel)

Me.Controls.Add(Me.mLastNameTextBox)

Me.Controls.Add(Me.mLastNameLabel)

EndInit() Calls CType(Me.mEmployeesBindingNavigator, System.ComponentModel.ISupportInitialize).EndInit()

Me.mEmployeesBindingNavigator.ResumeLayout(False)

Me.mEmployeesBindingNavigator.PerformLayout()

CType(Me.ControlBindingManager1, System.ComponentModel.ISupportInitialize).EndInit()

CType(Me.mPhotoPictureBox, System.ComponentModel.ISupportInitialize).EndInit()

CType(Me.mManagerPhotoPictureBox, System.ComponentModel.ISupportInitialize).EndInit()

CType(Me.mManagersBS, System.ComponentModel.ISupportInitialize).EndInit()

Variable Declarations Friend WithEvents ControlBindingManager1 As IdeaBlade.UI.WinForms.ControlBindingManager

Friend WithEvents mLastNameTextBox As System.Windows.Forms.TextBox

Friend WithEvents mFirstNameTextBox As System.Windows.Forms.TextBox

Friend WithEvents mBirthDateDateTimePicker As System.Windows.Forms.DateTimePicker

Friend WithEvents mAgeTextBox As System.Windows.Forms.TextBox

Friend WithEvents mOrdersCountTextBox As System.Windows.Forms.TextBox

Friend WithEvents mPhotoPictureBox As System.Windows.Forms.PictureBox

Friend WithEvents mManagerPhotoPictureBox As System.Windows.Forms.PictureBox

Friend WithEvents mManagerComboBox As System.Windows.Forms.ComboBox

Friend WithEvents mLastNameLabel As System.Windows.Forms.Label

Friend WithEvents mFirstNameLabel As System.Windows.Forms.Label

Friend WithEvents mBirthDateLabel As System.Windows.Forms.Label

Friend WithEvents mAgeLabel As System.Windows.Forms.Label

Friend WithEvents mOrdersCountLabel As System.Windows.Forms.Label

Friend WithEvents mPhotoLabel As System.Windows.Forms.Label

Friend WithEvents mManagerPhotoLabel As System.Windows.Forms.Label

Friend WithEvents mManagerLabel As System.Windows.Forms.Label

Friend WithEvents mManagersBS As System.Windows.Forms.BindingSource

When migrating code out of the designer code file, you will need the declarations, instantiations, and configuration

statements, though you may or may not need the BeginInit() and EndInit() statements. Without doubt, the

statements of most interest in the above are the statements that configure the ControlBindingManager (boldfaced).

There is a high likelihood that, even if you do not start out configuring bindings by hand, you will at some point find

yourself doing so, in order to use more advanced capabilities of DevForce WinClient data binding and/or to

Page 458: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

458 | P a g e

facilitate re-use of your databinding code. Many of the capabilities of DataConverters are available only from code,

and ViewDescriptors can only be used in code to create databindings.

Working with the DataGridViewBindingManager

Working with the DataGridViewBindingManager (DGVBM) is very similar to working with the

ControlBindingManager, except that properties dragged into the binding window will get bound to columns of the

DataGridView instead of loose controls.142

Notice that there is no “Autopopulate controls” option on the DGVBM‟s Smart Tag menu; nor are there separate

tabs for autopopulating and working with existing bindings. You can work with an existing grid control, already

present in the container (form or UserControl); or use the Create Grid button to have the designer create the grid on

your behalf.

It is best to let the DGVBM generate columns into the grid for you. Once it has done so, you can manipulate the

display properties of the columns using the .NET visual designer for the grid. 143

DevForce WinClient will retain

and work harmoniously with the settings you establish using the visual grid designer.

Working with the DataGridBindingManager

The DataGridBindingManager is designed for use with the .NET 1.1 DataGrid control (which was obsoleted by the

.NET 2.0 DataGridView control). It is included for those who need to work with legacy forms that use the older

grid.

If you are working entirely in .NET 2.0, you might want to consider deleting the DataGridBindingManager icon

from the toolbox.144

That way you will not select it accidentally when you mean to be working with the newer

DataGridView.

Working with BindingManagers for Third-Party Grids

DevForce WinClient supports the Developer Express XtraGrid control with a BindingManager called the

XtraGridBindingManager, and the Infragistics UltraGrid control with an UltraGridBindingManager. As mentioned

at the beginning of this section on BindingManagers, we have done everything possible to make the experience of

configuring this grid very similar to that of configuring the .NET DataGridView.

Synchronizing GridBindingManagers with UI Grid Designers

As of DevForce WinClient version 3.3, the developer can simultaneously use DevForce WinClient

GridBindingManagers to data bind grids to business objects and use the grid vendors UI Design tools to style the

grid. The DevForce WinClient GridBindingManagers retain columns and styles that you set with the grid vendor‟s

design tools, so you can now style the grid in the designer (where previously you could only do so in code).

The behaviors described in this section are supported beginning with the following grid versions: .NET 2.0

DataGridView; Infragistics v6.2.2 UltraGrid; and DevExpress v6.2 XtraGrid.

DESIGNING A GRID IN VISUAL STUDIO “DESIGN VIEW”

The DevForce WinClient GridBindingManager designers can generate and display columns in the corresponding

design time Grid. The result of a design “session” might look like this:

142 More accurately, the properties will get bound to controls embedded in cells of the DataGridView‟s columns.

143 This capability made its debut in DevForce WinClient version 3.3. Previous to that, configuration – but not creation – of the

grid had to be done in code.

144 Just select it and press the DELETE key.

Page 459: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

459 | P a g e

GridBindingManager designers stay synchronized with their corresponding design time grid layouts If a Grid‟s

design time layout changes, so will the GridBindingManager‟s and vice versa.

Caveat: If you delete a column in the grid designer, you must re-open the GridBindingManager designer so that it

can detect the change. You may have to delete the column again in the GridBindingManager to ensure that the

column actually disappears from the grid.

You can add an unbound grid column (columns that are not data bound to a data object property). Unbound

columns do not appear in the GridBindingManager‟s column listing but their positions and settings are

respected.

RUNTIME BEHAVIOR

Columns specified in a GridBindingManager do not replace the columns designed into the grid.

The GridBindingManager first tries to correlate its grid columns with those found in the grid itself.

Where there is a match, the GridBindingManager simply hooks up the data binding; it preserves the grid

column‟s visual styles, title, and position.

If the GridBindingManager holds a column that it cannot find in the grid, it adds that column to the grid and

styles it in the DevForce WinClient prescribed default manner. These columns appear after (to the right of) the

columns that were designed into the grid.

More on Third-Party WinForm Control Suites

Third-party UI control suites offer richer and more capable WinForm controls than those provided natively in .NET.

This is wonderful in itself but, in choosing to build with such suites, the developer faces an additional, often steep

learning curve.

We have no choice but to dive into the vendor‟s documentation if we wish to exploit the suite‟s full range of styling

and behavioral options. The vendor rarely follows the .NET standards for data binding and almost always raises

events in an idiosyncratic way.

DevForce WinClient can ease our development with two popular suites: Developer Express‟s “DXperience” and

Infragistics “NetAdvantage”.

We‟ve figured out many of the intricacies of these products and have developed product-specific analogs to the tools

and architectural elements we described for the .NET controls.

We‟ve just begun to develop this section of the UI chapter. There are only a few fragments. We will expand upon it

in a coming DevForce WinClient Developers Guide release.

Developer Express “DXperience”

DevForce WinClient offers support for v.6 of the Developer Express WinForm control suite.

Page 460: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

460 | P a g e

Most of the controls in this suite are (or were) branded with the “Xtra” prefix so we‟ll find that DevForce WinClient

constructs similarly prefixed, e.g., XtraGridManager.

Infragistics “NetAdvantage”

DevForce WinClient offers support for v.6 of the Infragistics WinForm control suite.

Most of the controls in this suite are (or were) branded with the “Ultra” prefix so we‟ll find that DevForce

WinClient constructs similarly prefixed, e.g., UltraGridManager.

Nested Grids in the UltraGridBindingManager

The UltraGridBindingManager class has an AddRelationBand method that implements a nested grid in an

Infragistics UltraWinGrid whose data binding is managed by an UltraGridBindingManager instance.

To implement such a grid, invoke this AddRelationBand method on the parent UltraGridBindingManager

instance.

For example, we want to display all Product objects belonging to a given Category when the user clicks a

Category row of an UltraWinGrid.

Assume the following:

An UltraGridBindingManager instance called categoryUltraGridBindingManager that displays

Category business objects.

An UltraGridBindingManager instance called productUltraGridBindingManager that displays

Product business objects belonging to a Category.

We‟ve bound grid columns to properties of the Category and Product business objects in their

respective binding managers.

We‟ve dropped a Category grid on the Form; we did not create a Product grid on the Form.

Never create a nested grid; the parent (outermost) grid will generate the nested grid(s) dynamically.

We add a single line to our Form initialization logic:

categoryUltraGridBindingManager.AddRelationBand( productUltraGridBindingManager, "Product", "Products")

Note that:

We add a relation band to the parent Grid Binding Manager.

The parameters are

o The subordinate Grid Binding Manager (productUltraGridBindingManager).

o The title of the nested grid (“Product”).

o The Relation Property of the parent object that returns the nested grid objects (“Products”).

DataBinders

DataBinders are classes that tailor the DevForce WinClient data binding architecture to particular Windows

Forms UI controls. They buffer idiosyncracies and syntactical differences among controls to that your data binding

experience is a smoother and swifter ride.

DevForce WinClient provides DataBinders for most of the scalar native .NET controls. DevForce WinClient

provides a DataBinder for one non-scalar .NET control, the DataGridView.

Page 461: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

461 | P a g e

DevForce WinClient also provides DataBinders for the Developer Express and Infragistics control suites. If, in

your UI, you mix the use of controls from these suites with .NET controls and/or with each other, you will discover

particular benefit from the buffering of annoying syntactical differences that DataBinders provide.

Custom DataBinders

You can write your own UI controls. If your custom control inherits from a .NET control or one of the supported

third-party controls, the data binding architecture will use the matching DataBinder for your control automatically.

This is often all you need.

If the matching DataBinder does not work for you or if you have written a control from scratch, you may need to

write your own DataBinder. Your custom DataBinder can inherit from an existing DevForce WinClient

DataBinder or from the DataBinder class itself.

Troubleshooting

Third-Party Control Suites

Upgrading a Suite and Assembly Redirection

Each DevForce WinClient release references a specific release of the vendor‟s controls. We cannot coordinate our

release schedule with those of third-party vendors. We do our best to keep up but we may lag a month or more

behind and, in that interim, the vendor may unleash a torrent of update patches, each with a new build number.

We cannot simply refer to their control suites by assembly name. We have to refer to them by their strong names.

We can only certify our product to work with the controls that we‟ve tested. Thus, our DLLs are locked to specific

versions of their DLLs.

Third-party control updates can break our code but most of the time the changes are transparent to DevForce

WinClient. Our focus is on the mechanics of binding to their controls. We usually are indifferent to the vendor‟s

improvements and repairs to control styles and behavior.

You, on the other hand, may want the latest control suite update – and want it right now because your application is

over a bug or infelicity. What to do?

Fortunately, .NET offers a technique called “Assembly Redirection”. With redirection you get to say, in effect,

“when the application asks for 3.2.1.3 of this DLL, please use the 3.2.2.0 version instead.” If .NET can find that

version, your application can continue.

While we cannot guarantee that our code will work with this alternate version it is certainly worth a try; our

Technical Support may be able to confirm that other customers are doing just fine with the vendor‟s release.

DevForce WinClient provides a tool named the Assembly Binding Redirector which is automatically launched

during installation if a non-supported version of a supported control suite is discovered on the target machine. The

tool compares assembly names in the discovered version against those in supported versions. Where there is a

match, it suggests redirecting references contained in the DevForce WinClient assemblies to the corresponding

assemblies in the discovered, installed version. It does so by adding assembly binding redirection statements to the

machine.config file on the target machine. (A backup is made of machine.config before any changes are made.)

The Assembly Binding Redirector is also available on the IdeaBlade DevForce WinClient Start menu (Start / All

Programs / IdeaBlade DevForce WinClient / Tools), so you can run it at any time after installation as well.

For the curious, more detail on assembly binding redirection is available in the DevForce WinClient Installation

Guide.

Page 462: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

462 | P a g e

UI Performance Tuning

UI performance tuning often involves decisions and strategies about when to load data, and what data to load.

Sometimes developers write code that loads data that isn‟t needed; sometimes we don‟t load data (in an efficient

way) that we are going to need momentarily.

Pre-Loading Data

If UI performance is sluggish due to data loading, consider pre-loading data using one or more span queries so that

subsequent data retrievals can be satisfied from the local cache. A very common scenario where a span query can be

helpful occurs when displaying nested properties in a grid. Suppose, for example, that you display a set of Orders in

a datagrid. You fetch all the needed Orders in a single query:

C#

BindableList<Order> _orders = new BindableList<Order>();

BindingSource _ordersBindingSource = new BindingSource();

DataGridViewBindingManager aDataGridViewBindingManager =

new DataGridViewBindingManager();

...

aDataGridViewBindingManager.BindingSource = _ordersBindingSource;

_ordersBindingSource.DataSource = _orders;

_orders.ReplaceRange(_entityManager.Orders.ToList());

VB

Dim _orders As New BindableList(Of Order)()

Dim _ordersBindingSource As New BindingSource()

Dim aDataGridViewBindingManager As New DataGridViewBindingManager()

...

aDataGridViewBindingManager.BindingSource = _ordersBindingSource

_ordersBindingSource.DataSource = _orders

_orders.ReplaceRange(_entityManager.Orders.ToList())

But in each row of the datagrid you have directed DevForce WinClient to display, among other Order properties, the

nested property Customer.CompanyName. DevForce WinClient can‟t supply the value of that property without

retrieving the Customer object where it lives, so as each row of the grid is populated, DevForce WinClient has to

submit a database query for the associated Customer object. If the Orders grid displays 100 Orders, you‟ve directed

the issuance of 100 distinct Customer queries!

Much better than this would be to use a span query to retrieve the Orders and the necessary Customers at the same

time:

C#

var aSpanQuery = _entityManager.Orders.Include("Customer");

_orders.ReplaceRange(aSpanQuery.ToList());

VB

Dim aSpanQuery = _entityManager.Orders.Include("Customer")

_orders.ReplaceRange(aSpanQuery.ToList())

Now, as the datagrid is being populated, the Customer associated with each Order can be retrieved at light-speed

from the local cache.

Page 463: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

463 | P a g e

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

WinClient TraceViewer can be extremely helpful. See the Object Persistence chapter for details.

Don‟t Load What You Don‟t Need

Sometimes complex forms with many tab pages or panels slow down because you are letting them load data that

isn‟t currently being displayed. In these cases, consider a strategy wherein you keep track of which containers are

visible at a given moment, and only load the data for those as the user navigates through collections of objects. This

can be accomplished by instantiating redundant BindingSources whose source lists are of the right type, but empty.

When a given container goes invisible, set the BindingSource(s) for its BindingManager(s) to the ones with the

empty lists. When it becomes visible again – as a TabPage does by becoming the selected tab – reassign the

BindingSource(s) to the ones with the real data – and keep that data current!

Don‟t Load What You Don‟t Need, Part 2

If you have a table with a large BLOB column and have contexts in your application where you need to display

other properties of the same entity, but not the BLOB, consider segregating the BLOB column into a separate table

related one-to-one with the main table. When object mapped, the BLOB column will then become a relation

property of the main object, and the host object for that relation property will not be retrieved unless the BLOB

relation property is specifically referenced in your data bindings or code.

If Control Configuration is the Bottleneck, Use SuspendLayout()

If data loading isn‟t the bottleneck, but instead the instantiation and configuration of controls, consider using the

SuspendLayout() and ResumeLayout() methods that are routinely used in the code generated by the Visual Studio

Form and UserControl designers. These suppress redundant and unnecessary layout events while your code is

adjusting property values for a control (including a container control).

Large BindingSource loads are Slow

It can be very slow to load large numbers of objects into the DataSource of a BindingSource bound to a grid. The

databinding apparatus may process each property of each grid row as it is added. The grid may perform better if it

only has to bind to the objects that are actually shown after you‟ve loaded the BindingSource.

You might try suspending the binding events while the loading the BindingSource (more accurately, while loading

the DataSource of the BindingSource). The BindingSource has a RaiseListChangedEvents property for that purpose.

Here's an example how to use it:

C#

aBindingSource.RaiseListChangedEvents = false;

aEntityList.ReplaceRange(GetEntityList()); // aBindingSource‟s DataSource

aBindingSource.RaiseListChangedEvents = true;

aBindingSource.ResetBindings(false); // Do DataBinding now

VB

aBindingSource.RaiseListChangedEvents = False

aEntityList.ReplaceRange(GetEntityList())' aBindingSource‟s DataSource

aBindingSource.RaiseListChangedEvents = True

aBindingSource.ResetBindings(False) ' Do DataBinding now

DevForce WinClient Assemblies for WinForm Support

Support for WinForm development is provided from the following assemblies:

Page 464: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce WinForm User Interfaces

464 | P a g e

Assembly Comments

IdeaBlade.UI

IdeaBlade.UI.Winforms

IdeaBlade.UI.Winforms.Design

IdeaBlade.UI.Winforms.DevExpressControls.vX_X Numerous assemblies targetted at different versions

(X_X) of the DevExpress control suite

IdeaBlade.UI.Winforms.DevExpressControls.vX_X.Design Numerous assemblies targetted at different versions

(X_X) of the DevExpress control suite

IdeaBlade.UI.Winforms.DotNetControls

IdeaBlade.UI.Winforms.DotNetControls.Design

IdeaBlade.UI.Winforms.InfragisticsControls.vX_X Numerous assemblies targetted at different versions

(X_X) of the DevExpress control suite)

IdeaBlade.UI.Winforms.InfragisticsControls.vX_X.Design Numerous assemblies targetted at different versions

(X_X) of the DevExpress control suite)

IdeaBlade.Util As distinguished from IdeaBlade.Core.

IdeaBlade.Util contains, in particular, the

BindableList<T> class.

Page 465: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Web Applications

465 | P a g e

Web Applications

Chapter 11: Web Applications The DevForce ASPDataSource Component Using the ASPDataSource in Development Overridable Methods for Select, Update, Insert, and Delete The EntityAdapterManager Class The Configure Data Source Wizard Parameter Collection Editor Retrieving Schema Information Third Party Support

DevForce provides object mapping and a persistence framework that are as useful in web applications as in

WinForm and WPF applications. For a web application, you deploy DevForce server-side. Communication between

the client and server tiers occurs between the user‟s browser and IIS; server-side, requests for data are handed off to

a two-tier DevForce application which communicates with the back end data sources.

The DevForce ASPDataSource Component

The datasource controls provided with ASPNET, notably the Object DataSource, are not designed to work with

objects that contain business logic, making them unsatisfactory for work with IdeaBlade business objects. By

contrast, the DevForce ASPDataSource control is an ASP.NET data source control designed to work with rich

business objects.

The ASPDataSource control is used at both run time and design time. Data binding occurs at run time. At design

time, functionality has been provided to allow Visual Studio 2005 developers to graphically create web pages using

common controls like the .NET DetailsView and GridView and third-party controls like the Developer

ExpressASPxGridView and ASPxPivotGrid, and the Infragistics UltraWebGrid.

Using the ASPDataSource in Development

The UI developer drags an ASPDataSource control onto a Web Form and interacts with it. In Visual Studio, the

ASPDataSourceDesigner exposes a Configure Data Source Wizard to permit the developer to configure an

instance of the ASPDataSource. When a control such as a .NET GridView bound to an ASPDataSource control

requests schema information about its data source, the schema information is created and returned. This allows

information to be returned not only about the simple properties backed by table columns, but also about custom

computed properties.

At runtime, the Web Form interacts with the ASPDataSource to perform the actual databinding. This work is

handled by the ASPDataSourceView, an instance of which is managed by the ASPDataSource control.

Overridable Methods for Select, Update, Insert, and Delete

Every ASP.NET DataSource control, whether provided in Visual Studio (e.g., the ObjectDataSource) or by a third

party vendor, allows the developer to specify methods and parameters for Select, Update, Insert, and Delete

methods. In many implementations, this is done by having the user select from a dropdown list supplied by a wizard

(as provided by the .NET ObjectDataSource) or by having the developer type in the method name into a property

Page 466: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Web Applications

466 | P a g e

sheet for the DataSource control (as done with the previous DevForceDataSource). The required parameters for

the CRUD method were specified in the code and were collected at runtime using a ParameterCollection Editor.

We found that typing in the CRUD method name and then making sure that the parameters specified in the code

matched the parameters collected by the Parameter Colllection Editor was a process that was both error-prone and

difficult to debug. For the ASPDataSource, we decided to take a different approach. We have declared four

abstract methods for Select, Update, Insert, and Delete which must be overridden by the developer to be used. By

requiring the developer to use overridable methods, many incorrectly written methods produce compilation errors

rather than runtime errors, and mysterious reflection errors are avoided.

Here are the signatures for the four abstract methods:

C#

public virtual IEnumerable SelectEntities(IOrderedDictionary parameters,

DataSourceSelectArguments pSelectArgs)

public virtual int InsertEntity(IOrderedDictionary parameters,

IDictionary newvalues)

public virtual int UpdateEntity(IOrderedDictionary parameters,

IDictionary keys ,IDictionary values, IDictionary oldvalues)

public virtual int DeleteEntities(IOrderedDictionary parameters,

IDictionary keys, IDictionary oldvalues)

The EntityAdapterManager Class

The code for each instance of an ASPDataSource control is contained in an EntityAdapterManager class. In the

Instructional Unit for this feature, there are three ASPDataSource controls:

mEmployeeSelectorAdapterManager, a DropDown control,

mEmployeeEditorAdapterManager, a DetailsView control; and

mOrderAdapterManager, a GridView control.

Every instance of an EntityAdapterManager has a read-only EntityTypeName (e.g, Employee or Order) and an

AdapterManagerAssemblyName (e.g., Web.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null)

Page 467: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Web Applications

467 | P a g e

The Configure Data Source Wizard

To prevent errors caused by mistyping the name of the EntityAdapterManager class, the developer is encouraged to

use a wizard. The ASPDataSource control has a wizard that provides a dropdown giving the developer the choice

of which EntityAdapterManager to use.

After the developer has selected an EntityAdapterManager, the EntityTypeName and EntityAssemblyName

properties are computed.

Parameter Collection Editor

The ASPDataSource control uses a Parameter Collection Editor to configure the parameters for each CRUD

method. For example, in the Instructional Unit, the EmployeeEditorAdapterManager needs the ID of the currently

selected employee in the DropDown list at the top of the Web Form to be able to perform its SelectEntities method.

Page 468: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Web Applications

468 | P a g e

Retrieving Schema Information

The ASPDataSource supplies schema information to the web control to which it is attached. A DetailsView control,

for example, needs to know the name, datatype, and other property information about the fields of the Entity that it

is editing. A GridView needs to know the same kind of information for the columns in its GridView. To signal its

ability to supply this information, an ASPDataSource control communicates its CanRefresh property. Then, the

WebControl calls into the ASPDataSource control to invoke the GetFields() function to get this schema

information. Once the Web Control has this information, it can display this information to the UI developer.

Third Party Support

The ASPDataSource control works well with a variety of .NET Web Controls such as the DropDownList,

DetailsView, and GridView; it works equally well with well-known third-party web controls such as the

ASPxGridView and ASPxPivotGrid from Developer Express, and the UltraWebGrid from Infragistics. We

suspect that the ASPDataSource will work with many other third-party controls that we have not specifically tested.

Page 469: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

469 | P a g e

Business Object Server

Business Object Server Business Object Server Architecture EntityService Startup and Shutdown EntityServer Startup and Shutdown Remote Service Method Call (RSMC) Methods Push Notification

BOS Hosting Details The DevForce Client

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

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

Business Object Server Architecture

The Business Object Server (BOS) is a deployable binary package that exposes a singleton instance of the DevForce

EntityService class. There are three ways to deploy the BOS, each of which supports a different deployment

scenario:

BOS Deployment DevForce Assembly Executable

Console Application ServerConsole.exe

Windows Service ServerService.exe

IIS Server - not applicable -

An IIS deployment doesn‟t need its own executable. It identifies the EntityService startup assembly in

a <service> tag of the Web.config such as

<service name="EntityService">

<endpoint

address=""

binding="customBinding" bindingConfiguration="compressedBinaryBinding"

contract="IdeaBlade.EntityModel.IEntityServiceContract" />

</service>

The EntityService class itself is part of the IdeaBlade.EntityModel.Server.dll which must be deployed

to the executing directory of the physical server.

Each of the three BOS deployments “wakes up” in a different way:

Page 470: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

470 | P a g e

BOS Deployment Wake up

Console Application When ServerConsole.exe is launched

Windows Service When the ServerService.exe Windows Service starts.

IIS Server When a EntityManager asks for service.

All three deployments The Business Object Server “wakes up” depending upon the nature of its deployment.

The singleton EntityService instance creates and manages one or more EntityServer instances. A

EntityService creates its first EntityServer when a EntityManager asks for BOS services.

One is plenty for many applications. But some applications will need more; to understand why, we must understand

the nature and purpose of “Datasource Extensions”.

Datasource Extensions

Every EntityManager is associated with a “Datasource Extension” (called the “extension” for short). The

“Extension” marks a collection of one or more data sources, each the repository of a set of business object classes

mapped to objects in that data source.

Our application integrates entities from each of these sources in a single, unified business model.

Deployment Extensions

There are excellent reasons to have multiple extensions. Many IT shops follow a rigorous deployment regime in

which each new version of the application progresses through a gauntlet of “environments” such as the

development, test, stage, and production environments.

Each environment has its own incarnation of the application data sources. Following our illustration, “Test” has

three data sources that parallel the “Development”sources; they differ only in their “connection strings” (or Web

Service equivalents).

DevForce supports this regime through named extensions. The “Test” extension identifies its data sources just as the

“Development” extension has its sources. A full-blown diagram might look like so:

Tenant Extensions

Extensions are also a good way to segment data sources by client in a “multi-tenant application”. Multi-tenant

applications are typical of Application Service Provider (ASP) scenarios in which each customer‟s data are managed

in isolated datasources.

Page 471: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

471 | P a g e

When the user logs in, the application identifies the user‟s parent customer and knows which set of data sources is

appropriate for that user. The application can then instantiate a EntityManager that draws upon just those data

sources.

The “Datasoure Extension” is the ideal representation for a customer-specific data source set as in this depiction of

a three-tenant scenario with customers “A”, “B”, and “C”:

Extensions and EntityServers

Let‟s stick with the multi-tenant, ASP scenario for awhile.

When the application client determines the customer, it creates a EntityManager dedicated to the data sources

applicable to that customer by including the customer‟s “Datasource Extension” name in the constructor.

msManager = new EntityManager(true, "A"); // Connect to customer "A"

Now the client application tries to login or fetch entities with this EntityManager. The EntityManager contacts

the EntityService. The EntityService checks among its EntityServers for one that is associated with

extension “A”. It doesn‟t find one so it creates a new EntityServer instance for extension “A” and adds it to its

collection. This EntityServer now serves every EntityManager presenting the “A” extension.

When the EntityService encounters EntityManagers with unknown extensions – “B” and “C” for example –,

it creates more EntityServers. The three-tenant scenario could look like this:

Review

Page 472: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

472 | P a g e

The components intrinsic to and orbiting the Business Object Server have confusingly similar names. Here’s a brief

review

BOS-Related Components Purpose and Function

Business Object Server The deployed incarnation of the EntityService. Shipped in three

flavors: console application, Windows Service, and IIS.

EntityService A singleton instance of this class handles the DevForce “Business Object

Server” functions on a physical middle tier. It creates and manages one

or more EntityServers, each identified by its “Datasource Extension”.

EntityServer Performs server functions pertainting to a set of data sources. Those data

sources are collectively identified by a “Datasource Extension.”

The EntityService routes EntityManager requests to the

EntityServer whose extension matches the EntityManager‟s

extension.

EntityManager The client-side manager of business objects. The EntityManager

executes in the client-layer of the application. It makes requests for

entities, authentication, and other services to the EntityService which

routes those requests to the EntityServer whose “Datasource

Extension” matches the requesting EntityManager‟s extension.

EntityServiceApplication We haven‟t discussed this yet but we are about to. See “EntityService

Startup and Shutdown”.

EntityService Startup and Shutdown

Our application may need to do some initial processing when the singleton EntityService starts up. It might need

to launch auxiliary server-side processes for example. Perhaps it should alter the in-memory copy of the IdeaBlade

Configuration file before creating its first EntityServer.

Perhaps our application should run server-side clean-up code when it shuts down. For example, it might shut down

the auxiliary services it started or send an email alert reporting that the service is coming down.

Out of the box the Business Object Server wakes up and goes straight to work. We need to do a little programming

if we want these startup and shutdown behaviors. We will create and register a sub-class of the DevForce

EntityServiceApplication class to do our pre- and post-processing.

One of the first steps for a new EntityService singleton is to acquire the singleton instance of a class that

derived from EntityServiceApplication. Such a class has two methods of obvious purpose:

public virtual void OnServiceStartup(object sender, ServiceStartupEventArgs e) public virtual void OnServiceShutdown(object sender, EventArgs e)

The EntityService looks for a EntityServiceApplication class in an assembly named in one of the global

probe assembly names listed in the IdeaBlade Configuration File.

It asks for a singleton instance of the first such class it discovers. If it can‟t find such a class, it obtains the singleton

instance from the base EntityServiceApplication class in the DevForce EntityModel library.

The EntityService then calls the EntityServiceApplication.OnServiceStartup method. When the

EntityService terminates, its finalizer calls OnServiceShutdown.

Invoking OnServiceShutdown inside the EntityService finalizer all but guarantees that the method

will be called, even if the EntityService dies by exception. Make sure that your implementation

“cannot fail” and does not raise an exception of its own – a no-no inside finalizers.

Page 473: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

473 | P a g e

Adding custom pre- and post- behaviors

There are two steps to adding custom behavior:

Write a subclass of EntityServiceApplication that overrides these methods.

The overrides should call the base EntityServiceApplication class methods.

Identify its assembly in the server-side IdeaBlade Configuration File.

Mention the assembly in the top level, “global” probe assembly path. If the class is in an assembly named “Server”,

the XML might read:

XML

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

<configuration>

<ideaBlade.configuration version="5.00">

<probeAssemblyNames>

<probeAssemblyName name="Server" />

</probeAssemblyNames>

</ideaBlade.configuration>

</configuration>

EntityServer Startup and Shutdown

The EntityService singleton calls EntityServiceApplication methods when it starts and stops.

Remember that there is only one instance of a EntityService on a given physical middle tier. This

EntityService instance creates and manages one or more EntityServer instances that do the heavy-lifting for a

set of data sources at the behest of client EntityManagers.

As this is written, there are no EntityServer equivalents to “OnServiceStartup” or “OnServiceShutdown”. We

can imagine scenarios that justify “OnServerStartup” and “OnServerShutdown” methods as well as methods for

other EntityServer events. We are prepared to add them to the EntityServiceApplication class when we

have concrete use cases for them. Please let us know if you have such cases.

Remote Service Method Call (RSMC) Methods

EntityManager has an InvokeServerMethod() method that facilitates the running of server-side-only methods. The

following signatures are available for the EntityManager’s InvokeServerMethod() method:

// Old signatures

public Object InvokeServerMethod(

Page 474: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

474 | P a g e

String pTypeName,

String pMethodName,

params Object[] pArgs)

public Object InvokeServerMethod(

ServerMethodDelegate pDelegate,

params Object[] pArgs)

Use the first overload when the method to be invoked does not reside in any client-side assembly (e.g.,

DomainModel.dll). When the method is available client-side, the second overload can be used, and tends to be a

bit less vulnerable to the introduction of errors in the type and method names.

First overload:

C#

string typeName = "DomainModel.OrderSummary,DomainModel";

string methodName = "GetNumberOfOrders";

int num = (int)_em1.InvokeServerMethod(typeName, methodName,

10, new DateTime(1995, 1, 1), new DateTime(1999, 1, 1)); //pArgs

VB

Second overload:

C#

ServerMethodDelegate delegate =

new ServerMethodDelegate(OrderSummary.GetNumberOfOrders);

int num = (int)_em1.InvokeServerMethod(delegate,

10, new DateTime(1995, 1, 1), new DateTime(1999, 1, 1)); //pArgs

Here is the method to be invoked server-side. Note the [AllowRpc] attribute, without which the

InvokeServerMethod call will result in an exception.

C#

[AllowRpc]

public static Object GetNumberOfOrders(IPrincipal pPrincipal,

Page 475: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

475 | P a g e

EntityManager pPm, params Object[] pArgs) {

return GetOrderCount(pPm, pArgs);

}

VB

Push Notification

The “push” feature allows client applications to “subscribe” to server-side code running on the BOS, and to receive

periodic user-defined notifications from the server throughout the client‟s lifetime. This feature is not available in

DevForce Silverlight.

A client calls one of the EntityManager‟s RegisterCallback overloads to register or subscribe to a push service. It

supplies information both about the server method to be monitored and its own method to be called for notification

of server activity. Additional parameters supply a userToken that uniquely identifies a particular subscription

request, and user-defined arguments to be passed to the service.

C#

public void RegisterCallback(ServerNotifyDelegate serverDelegate, ClientNotifyDelegate clientDelegate, Object userToken, params Object[] clientArgs);

Once registered, the client remains subscribed to the service until either calling CancelCallback or closing. The push

“service” is somewhat analogous to the familiar server-side method callable by the InvokeServerMethod call. The

method must have a ServerNotifyDelegate signature, and will use the passed INotificationManager to communicate

with subscribers.

C#

public delegate void ServerNotifyDelegate(Guid serviceKey, INotificationManager notificationManager, EntityManager serverEntityManager);

The method runs on its own thread, and can perform any processing desired, including starting additional threads or

processes. The method is currently started upon first client subscription, and stopped after the last client

unsubscribes. To obtain information about its subscribers, it can use the INotificationManager.GetSubscribers

method. This will return all current subscribers, and include the IPrinicipal of the client and any client arguments

passed. The INotificationManager.Send method is used to send service-defined data to subscribers – allowing either

a broadcast to all subscribers or to only an individual.

Page 476: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

476 | P a g e

These “broadcasts” are received by client applications in the method specified when registering the callback.

C#

public delegate void ClientNotifyDelegate(object pUserToken, params Object[] pArgs);

The data passed must be serializable (and currently has the same restriction as with the InvokeServerMethod in that

Entities may not be directly passed or returned). A Learning Unit entitled “Server Push Notification” contains

examples of this feature.

BOS Hosting Details

Transports

The EntityService and EntityServers are all implemented as WCF services. If your BOS is hosted by either the

ServerConsole.exe or ServerService.exe, you can choose to use either HTTP (including HTTPS) or “net.tcp”

transports. (You can also choose “net.pipe” for named pipe cross process communication on a single machine, but

this is only useful during development.) Note that to use TCP you must specify a remoteBaseUrl beginning with

“net.tcp://” since this is the new naming convention used by WCF.

Configuration

In the app.config file on the server, use the ObjectServer element to configure a BOS with DevForce defaults.

Example:

XML

<objectServer

isDistributed="true"

remoteBaseURL="http://localhost"

serviceName="EntityService"

serverPort="9009"

sessionEncryptionKey=""

/>

You will probably need to manually open the port used by your service. If using Windows Firewall, use

the “Add Port” button on the Exceptions tab. Be sure to choose a TCP port even if you‟re using the HTTP

Page 477: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

477 | P a g e

protocol.

If you do use named pipes, remember it works on the localhost only, and do not specify a serverPort.

For more advanced scenarios, you‟ll need a ServiceModel configuration section in your app.config file to configure

the service. Some possible reasons you might want to use this approach would be to add channel security or modify

default settings (eg, for buffer sizes and timeouts).

Advanced Service Configuration Details

You can skip this section if you don‟t think you have the need, or the WCF knowledge, to customize the service

configuration using an app.config. Read on if you want the details to help understand what defaults are provided.

The BOS services require a custom message encoder called GzipMessageEncoding, which transmits all messages in

compressed binary format. Because we use a custom message encoder we cannot use the standard WCF bindings

such as “basicHttpBinding”, “wsHttpBinding”, “netTcpBinding”, etc., but instead use the “customBinding” type,

which requires that we explicitly specify all elements in the binding. We default to a customBinding stack

containing the following binding elements: 1) gzipMessageEncoding and 2) a transport as specified by the protocol

scheme in the RemoteBaseURL (HttpTransportBindingElement, HttpsTransportBindingElement,

TcpTransportBindingElement, or NamedPipeTransportBindingElement).

The BOS actually consists of at least two services: 1) the “EntityService” which functions as a factory for creation

of EntityServers, and 2) a EntityServer for each data source extension used.

For each service, a single endpoint is created with an address based on the URI built from elements in the

ObjectServer section of the app.config file. For an EntityServer, the address includes the name “EntityServer”

followed by the data source extension. The transport is assigned based on the protocol scheme, and the

MaxReceivedMessageSize is set to the maximum value – allowing for up to 2G to be transmitted in a fetch or save.

All other transport values default based on the type of transport used. The GzipMessageEncoding uses the defaults

for the underlying BinaryMessageEncoding, with the MaxArrayLength ReaderQuota set to the maximum value –

this is again to allow for large amounts of data to be fetched and saved. Default timeout values are used for open,

close, send and receive. If using net.tcp, a ServiceThrottlingBehavior is used to set the MaximumConcurrentCalls

and MaximumConcurrentSessions to 100 (from the default of 10).

Configuration for IIS Deployment

You must use a web.config file to configure the BOS when hosted by IIS. We provide sample web.config files in

the LearningResources\110_Deployment\Snippets\IIS Files folder. The sample files should be sufficient for most

uses – see the descriptions above regarding modifying the server app.config file if you need more information or

must customize the configuration. If you have the .NET 3.0 SDK installed, use the Service Configuration Editor to

help edit this file. The sample web.config also configures diagnostic message logging. You can use the Service

Trace Viewer utility, available with the SDK, to view the log.

If you‟re familiar with WCF, you know that a .svc file is needed for each of your services in “hosted” environments

such as IIS. For the BOS this means you‟ll need a file named EntityService.svc, and a .svc file for each EntityServer

based on the data source extensions used. These files should be placed in your virtual directory. Here are examples

of each:

EntityService.svc

IIS

<%@ServiceHost language="C#"

Service="IdeaBlade.EntityModel.Server.RemoteEntityService"

Page 478: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

478 | P a g e

%>

The service file for the EntityServer is a little trickier – the file name must be formed using the name “EntityServer”,

an underscore, and the data source extension. If no data source extension is needed, then the file should be named

“EntityServer.svc”. But there‟s another wrinkle too, we need to provide the data source extension as an argument to

the Service, and specify a Factory too. Here are some samples:

EntityServer.svc

IIS

<%@ServiceHost language="C#"

Service="IdeaBlade.EntityModel.Server.EntityServer"

Factory="IdeaBlade.EntityModel.Server.ServerHostFactory"

%>

EntityServer_Dev.svc – for a data source extension of “Dev”

IIS

<%@ServiceHost language=”C#”

Service="IdeaBlade.EntityModel.Server.EntityServer, Dev"

Factory="IdeaBlade.EntityModel.Server.ServerHostFactory"

%>

The DevForce Client

On the client, you need an app.config with service information that matches the corresponding settings used for the

server. The following app.config excerpt, for example, matches the server-side example provided above, with only

the addition of proxy information:

Page 479: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

479 | P a g e

XML

<objectServer

isDistributed="true"

remoteBaseURL="http://localhost"

serviceName="EntityService"

serverPort="9009"

proxyName=""

proxyPort="0"

sessionEncryptionKey=""

/>

Be sure that the remoteBaseURL points to the correct server, and that the protocol scheme, port and serviceName

are the same as is used on the server.

As with the BOS, you can use a client-side app.config to customize your configuration. As is true with the BOS,

you should have some WCF expertise to do this, and use the Service Configuration Editor from the .NET 3.0 SDK

to help.

Vista Setup

The following material applies when running the BOS under the Windows Vista operating system.

Vista setup requirements for the ServerConsole or ServerService

You receive an AddressAccessDeniedException telling you that HTTP could not register your URL because you do

not have access rights to the namespace. This is caused because the process (or service) is not running with

administrator privileges and HTTP addresses are secured resources. You have two options:

1. Run the process (or service) with an administrative account.

2. Run the Netsh command line tool to register the namespace with the account. The steps are as follows:

a. Open a command prompt using “Run as administrator” and enter:

b. netsh http add urlacl url=http://+:9009/ user=DOMAIN\USERNAME

...where “9009” is the port you are using for the BOS, and DOMAIN\USERNAME is the system account to

be granted access.

Also see http://blogs.msdn.com/drnick/archive/2006/10/16/configuring-http-for-windows-vista.aspx for more

information.

Vista setup requirements for IIS

Since the BOS is a WCF service, you must ensure that IIS, WCF and the WCF activation component are installed

and registered. Both WCF and IIS must be installed for IIS-hosted WCF services to function correctly. The

installation process for the .NET Framework 3.0 automatically registers WCF with IIS if IIS is already present on

Page 480: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

480 | P a g e

the machine. If IIS is installed after the .NET Framework 3.0, an additional step is required to register WCF with IIS

and ASP.NET. You can do this as follows, depending on your operating system:

Windows XP SP2 and Windows Server 2003: Use the ServiceModelReg.exe tool to register WCF with

IIS: To use this tool, type ServiceModelReg.exe /i /x at a command prompt.

Windows Vista: Install the Windows Communication Foundation Activation Components subcomponent

of the .NET Framework 3.0. To do this, in Control Panel, click Add or Remove Programs and then

Add/Remove Windows Components. This activates the Windows Component Wizard.

Troubleshooting

Worked in 2-Tier, Strange Errors in n-Tier

Applications generally do not have 2-tier dependencies that cause an n-tier application to fail. The hardest part of

going n-tier is setting up the environment of the middle tier, a topic covered in the Deployment chapter.

We assume in this section that you have established your ability to connect to the middle tier (the Business Object

Server) and can see some interaction between client and server.

Mismatched Client-side and Server-side Model Libraries

DevForce applications must have identical Model-related DLLs on both client and middle tiers. Mismatched class

libraries are the number one cause of strange n-tier behavior.

The UI class libraries do not belong on the middle tier. This topic concerns the Model DLLs such as those holding

your business object entities. They could include Model helper assemblies if you have them, such as a separate

library holding Id generation or Login-logic.

Proper versioning of each build is the best protection against mismatched DLLs. .NET detects the mismatch and

reports it the moment that a business object entity starts moving across the tiers.

Unfortunately, many people neglect to bump the version number with each new build. We forget to do it too.

Perhaps there is no difference in the code but the build was done with different configurations: a debug

configuration in one case and a release configuration in the other.

We get away with it for awhile and then one day we publish a business object model for clients but neglect to deploy

it to the middle tier. The client and server model DLLs have the same version numbers so .NET permits them to

interoperate. But they are not actually the same and a subtle incompatibility yields weird results or an exception.

Do confirm that the Model DLLs on both the client and server have the same version number, the same file

date, and the same file size.

Please understand that technical support will ask for evidence that this is so. Time and again we discover that the

DLLs are not the same despite numerous verbal assurances that they are. We may ask you to re-deploy the libraries

directly from the client side just to be sure. We appreciate your patience.

Mismatched IdeaBlade Class Libraries

The IdeaBlade class library versions must be the same on both client and middle tiers. If you‟ve recently upgraded

DevForce, you should confirm that the same DLLs are also on the server.

Page 481: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Business Object Server

481 | P a g e

“Maximum concurrent users limit met or exceeded” Exception

You might see this error in several situations when you have a limited-user license. It may seem incorrect, since in

some cases you know that you do not have more concurrent users than your license allows. So why are you getting

the error? DevForce decrements the user count under two conditions:

when Logout is called, and

after 30 minutes of inactivity.

Be sure to call EntityManager.Logout() when you close your application, and you‟ll avoid this problem.

During testing, you may find that you want or need to exceed your user license. DevForce provides this option with

a setting in the server‟s config file. Set the testMode attribute to true, like so:

XML

<ideaBlade.configuration version="5.00" testMode="true">

Test mode supports an unlimited number of users for a period of one hour.

Check the debuglog.xml output on your BOS for messages relating to this feature. You will see messages both when

test mode starts, and again when the test mode interval has elapsed. After the test mode interval has elapsed, all

connection attempts by clients will be refused, and these refusals will be seen in the log also.

After the test mode interval has elapsed, you will need to restart the BOS if you need to continue in this mode.

To turn test mode off, either remove the attribute from the <ideaBlade.configuration> element, or set the value to

“false”.

Page 482: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

482 | P a g e

Disconnected Applications

Disconnected Applications Running Offline Securing Offline Data

Many applications enable end users to work on data while offline. Users of Microsoft Outlook, for example, can

review and create new emails while on a plane or in some other disconnected location. Changes can be saved back

to the host database when the connection is restored.

DevForce can help build such applications.

Client-side caching makes it possible to operate disconnected from the host for extended periods, insulating the

client from connection problems. The end-user can keep working as long as the client application itself keeps

running.

The application may not be able to perform all of its functions while disconnected. It is up to the developer

to regulate what can and cannot be done in a disconnected state.

The end-user must shut down eventually. What to do if there is no connection then or if the application will be

resumed without access to the host? The application must be able to save “state” locally and restore such state when

the application restarts.

DevForce provides three essentials for an application that can thrive off-line:

A serialized representation of entity state – the EntityCacheState.

The ability to save that state somewhere – the SaveCacheState methods.

The ability to retrieve and restore entity state – the RestoreCacheState methods.

The EntitySet contains a binary serialization of entity persisted state and information related to those entities.

While designed to hold the contents of an entire EntityManager it can as easily contain selected entities (such as

those which have been modified – a point to which we will return later).

The EntitySet does not preserve non-persisted state such as the values of custom fields you‟ve added to

your business objects. (Values of custom properties that are based on persisted fields will, of course, be

computed as needed from their definitions in your business classes.)

DevForce EntityManager‟s “EntitySet” methods shuttle business object data between the EntityManager‟s in-

memory entity cache and local storage.

Page 483: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

483 | P a g e

The SaveCacheState methods convert object state to “EntitySet” format.

C#

void SaveCacheState(string fileName);

void SaveCacheState(IEnumerable entities, string fileName)

void SaveCacheState(Stream stream, bool closeOnExit)

void SaveCacheState(IEnumerable entities, Stream stream, bool closeOnExit)

VB Sub SaveCacheState(fileName As String)

Sub SaveCacheState(entities As IEnumerable, fileName As String)

Sub SaveCacheState(stream As Stream, closeOnExit As Boolean)

Sub SaveCacheState(entities As IEnumerable, stream As Stream, closeOnExit As Boolean)

SaveCacheState can preserve the entire entity cache, or just some selected objects. The destination file may reside

anywhere in the local file system although it is often wise to save to the user's "Isolated Storage". The "stream"

signatures facilitate piping the data through intermediate filters before they get to their ultimate destination. For

example, we might want to flow object data through an encryption filter before storing them. You can also append

custom bytes to the same stream if you're up to managing that yourself.

When the user re-launches the application, it should locate the saved file (or stream) and restore its contents to

the EntityManager’s entity cache, using one of the following methods (in

EntityManager.CacheStateManager):

C#

void RestoreCacheState(string fileName)

void RestoreCacheState(string fileName, RestoreStrategy strategy)

void RestoreCacheState(Stream stream, RestoreStrategy strategy, bool closeOnExit)

void RestoreCacheState(EntityCacheState entityCacheState)

void RestoreCacheState(EntityCacheState entityCacheState, RestoreStrategy strategy)

VB Sub RestoreCacheState(String fileName)

Sub RestoreCacheState(String fileName, RestoreStrategy strategy)

Sub RestoreCacheState(Stream stream, RestoreStrategy strategy, Boolean closeOnExit)

Sub RestoreCacheState(EntityCacheState entityCacheState)

Sub RestoreCacheState(EntityCacheState entityCacheState, RestoreStrategy strategy)

The RestoreStrategy parameter prescribes how to handle incoming cache state objects that already exist in the

target EntityManager‟s cache. The default RestoreStrategy preserves the cache's pending business objects

changes – additions, modifications, and deletions.

Running Offline

Offline applications are the result of careful design. They can‟t be generated automatically. The developer must

think through:

What entities must be available offline? How much data can we keep locally?

What entities can be changed? Which entities can be added or deleted?

What operations are permitted while offline?

How do we communicate to users these differences between online and offline capabilities?

How should the application transition back online?

How do we resolve concurrency conflicts when it is much more likely that another user will have modified a

record mapped to an object we changed?

Are there security consideration? What if the laptop is stolen?

Preparing for Offline Data

Page 484: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

484 | P a g e

We can only work disconnected with data that are available locally. While disconnected, queries and object

navigation can only access cached entities. The application must anticipate the user's disconnected data needs.

Many applications pre-fill the cache with entities the user will need offline before saving that cache and

disconnecting.

“Saving” While Offline

While offline, the EntityManager‟s SaveChanges() method can‟t help you: there is no connection pathway to the

datasource. But DevForce provides a pair of additional functions, SaveCacheState() and RestoreCacheState(), to

give you a means of storing the contents of the EntityManager‟s cache to, and retrieving them from, a local disk file.

Saving the Cache Contents to a Local Disk File

The EntityManager‟s SaveCacheState() method writes the contents of the cache to a binary, unencrypted local disk

file. There are several overloads:

Overload Description

Page 485: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

485 | P a g e

C#

public void SaveCacheState(

string pFileName

)

VB

Overloads Public Sub SaveCacheState( _

ByVal pFileName As String _

)

Stores the state of this EntityManager

and all cached entities to a file system file.

C#

public void SaveCacheState(

IEnumerable pDataRows,

string pFileName

)

VB

Overloads Public Sub SaveCacheState( _

ByVal pDataRows As IEnumerable, _

ByVal pFileName As String _

)

Stores the state of this EntityManager and the specified entities to a file system file.

By using a non-generic BindableList or EntityList for the pDataRows parameter, you can mix disparate

Entity types.

C#

public void SaveCacheState(

Stream pStream,

bool pCloseOnExit

)

VB

Overloads Public Sub SaveCacheState( _

ByVal pStream As Stream, _

ByVal pCloseOnExit As Boolean _

)

Stores the state of this EntityManager and all cached entities to a stream.

C# public void SaveCacheState(

IEnumerable pDataRows,

Stream pStream,

bool pCloseOnExit

)

VB Overloads Public Sub SaveCacheState( _

ByVal pDataRows As IEnumerable, _

ByVal pStream As Stream, _

ByVal pCloseOnExit As Boolean _

)

Stores the state of this EntityManager and the specified entities to a stream.

By using a non-generic BindableList or EntityList for the pDataRows

parameter, you can mix disparate Entity types.

Here is some sample code that uses the first-listed overload of SaveCacheState() to write the cache contents to a disk

file:

Page 486: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

486 | P a g e

C#

private void StoreOffLine() {

string path = Application.UserAppDataPath + "\\MyLocalData.bin";

try {

if (! (Directory.Exists(Application.UserAppDataPath))) {

Directory.CreateDirectory(Application.UserAppDataPath);

}

_entityMgr.CacheStateManager.SaveCacheState(path);

MessageBox.Show("Changes saved to local storage");

}

catch (Exception ex) {

MessageBox.Show(ex.Message);

}

}

VB Private Sub StoreOffLine()

Dim path As String = Application.UserAppDataPath + "\MyLocalData.bin"

Try

If Not Directory.Exists(Application.UserAppDataPath) Then

Directory.CreateDirectory(Application.UserAppDataPath)

End If

_entityMgr.CacheStateManager.SaveCacheState(path)

MessageBox.Show("Changes saved to local storage")

Catch ex As Exception

MessageBox.Show(ex.Message)

End Try

End Sub

You‟ll see an example of one of the overloads that writes to a MemoryStream in the section, “Saving the Cache

Contents to an Encrypted Local Disk File”.

Retrieving the Cache Contents from a Local Disk File

The EntityManager‟s RestoreCacheState() method reads the contents of a binary, unencrypted local disk file into the

local cache. As with SaveEntitytSet(), there are several overloads:

Page 487: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

487 | P a g e

Overload Description

C#

public void

RestoreCacheState(

EntitySet pEntitySet

)

VB

Overloads Public Sub

RestoreCacheState( _

ByVal pEntitySet As

EntitySet _

)

Merges an EntitySet into this EntityManager using the

NormalRestoreStrategy.

A normal RestoreStrategy replaces the SaveOptions and

QueryOptions in the current cache with those saved in the incoming

EntitySet; and uses a MergeStrategy of PreserveChanges.

C#

public void

RestoreCacheState(

EntitySet pEntitySet,

RestoreStrategy pStrategy

)

VB

Overloads Public Sub

RestoreCacheState( _

ByVal pEntitySet As

EntitySet, _

ByVal pStrategy As

RestoreStrategy _

)

Merges an EntitySet into this EntityManager using the

RestoreStrategy specified.

C#

public void

RestoreCacheState(

string pFileName

)

VB

Overloads Public Sub

RestoreCacheState( _

ByVal pFileName As

String _

)

Restores entities from a file system file into this

EntityManager using the NormalRestoreStrategy.

C#

public void

RestoreCacheState(

string pFileName,

RestoreStrategy pStrategy

)

Restores entities from a file system file into this

EntityManager using the RestoreStrategy specified.

Page 488: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

488 | P a g e

VB

Overloads Public Sub

RestoreCacheState( _

ByVal pFileName As

String, _

ByVal pStrategy As

RestoreStrategy _

)

C#

public void

RestoreCacheState(

Stream pStream,

RestoreStrategy

pStrategy,

bool pCloseOnExit

)

VB

Overloads Public Sub

RestoreCacheState( _

ByVal pStream As

Stream, _

ByVal pStrategy As

RestoreStrategy, _

ByVal pCloseOnExit As

Boolean _

)

Restores entities from a stream into this PeristenceManager

using the RestoreStrategy specified.

Note that several of the overloads of RestoreCacheState() take a RestoreStrategy. Here some sample code to

instantiate and use one of those:

C#

RestoreStrategy aRestoreStrategy =

new RestoreStrategy(true,true,MergeStrategy.PreserveChanges);

_entityManager.CacheStateManager.RestoreCacheState(path, aRestoreStrategy);

VB

Dim aRestoreStrategy As New RestoreStrategy( _

pRestoreSaveOptions:=True, pRestoreQueryStrategy:=True, _

pMergeStrategy:=MergeStrategy.PreserveChanges)

_entityManager.CacheStateManager.RestoreCacheState(path, aRestoreStrategy)

The VB code shows the parameter meanings most clearly: you get to specify whether you wish to replace the

SaveOptions and QueryOptions in the current cache with those saved in the incoming EntitySet; and just how you

want incoming entities merged with entities that may already be present in the cache at the time of the restore.145

145 MergeStrategies include NotApplicable, OverwriteChanges, PreserveChanges, PreserveChangesUnlessObsolete, and

PreserveChangesUpdateOriginal.

Page 489: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

489 | P a g e

The following code uses the first-listed overload of RestoreCacheState() to load the contents of a stored EntitySet to

the local cache:

C#

private void LoadOffLine() {

string path = Application.UserAppDataPath + "\\MyLocalData.bin";

if (File.Exists(path)) {

try {

_entityManager.RestoreCacheState(path);

_employees = entityManager.Employees;

_employeesBS.DataSource = _employees;

_managersBS.DataSource = _employees;

}

catch (Exception pException) {

MessageBox.Show(pException.Message);

File.Move(path, path + "\\\\" + DateTime.Now.ToString());

MessageBox.Show("Local data file not found!");

}

}

}

VB

Private Sub LoadOffLine()

Dim path As String = Application.UserAppDataPath + "\MyLocalData.bin"

If File.Exists(path) Then

Try

_entityManager.RestoreCacheState(path)

_employees = _entityManager.Employees

Me._employees BS.DataSource = _employees

Me._managersBS.DataSource = _employees

Catch pException As Exception

MessageBox.Show(pException.Message)

File.Move(path, path + "\\" + DateTime.Now.ToString())

MessageBox.Show("Local data file not found!")

End Try

End If

End Sub

You‟ll see an example of one of the overloads that reads from a MemoryStream in the section, “Retrieving the

Cache Contents from an Encrypted Local Disk File”.

Saving a Subset of the Cache to Local Storage

New entities, and entities with changes, constitute the most critical data in the cache, from a persistence viewpoint.

The application could terminate unexpectedly due to a power loss or unhandled exception. Absent a regular,

automatic backup of unsaved changes, they would be lost. On the other hand, repeated saves of the entire cache

could become quite cumbersome with a large cache, adversely impacting your user‟s UI experience.

Wouldn‟t it be nice to be able save to local storage, not the entire contents of the cache, but a subset thereof – say,

just the new and modified items? Can you think of other reasons you‟d like to be able to save only a subset of the

cache to local storage? Fortunately, the overloads of SaveCacheState() that take a DataRows parameter permits you

to do just this.

Here‟s an example that saves all entities with pending (unsaved) changes:

Page 490: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

490 | P a g e

C#

private void SaveChangesLocally() { // C#

string fileName = "AppPendingChanges"; // ToDo: use encrypted stream instead

DataRowState changes =

DataRowState.Added | DataRowState.Deleted | DataRowState.Modified;

MainPm.Manager.SaveCacheState(MainPm.Manager.GetEntities(changes), fileName);

}

VB

Private Sub SaveChangesLocally() ' VB

Dim filename As String = "AppPendingChanges" ' ToDo: use encrypted stream

Dim changes As DataRowState = _

DataRowState.Added Or DataRowState.Deleted Or DataRowState.Modified

MainPm.Manager.SaveCacheState(MainPm.Manager.GetEntities(changes), fileName)

End Sub

Of course if we are to run off-line, it will be important to save the unmodified entities too. But we can do that

infrequently to one file (“AppUnmodified”) and subsequently save just the pending changes to a second file

(“AppPendingChanges”). We (over)write that second file frequently. When we re-launch the application, after

intentional or accidental shutdown, we rebuild the cache state by loading first the “AppUnmodified” file and then

the “AppPendingChanges” file.

Autosave

Even if we don‟t want our application to run disconnected, we may still want to protect users from catastrophic

failures. Microsoft Word has an “autosave” feature that preserves changes made in the last minutes; it can discover

that the application did not shut down properly and offer the user the chance to restore those changes. Wouldn‟t that

be a great feature for our application?

The easiest approach is to save changes to the host every few minutes. But that may not be possible. The changes

may not pass validation checks and there may be no good place on the host to save invalid data.

Many of the techniques for offline applications are suitable for coping with this problem. In brief, we can

Set up an “autosave” timer

Save (and encrypt) just the changed entities to a recovery file.

Reset the recovery file everytime we succeed in saving to the host.

Establish secure and consistent recovery techniques that detect improper shutdown and restore to cache the

cache state file with pending changes.

Temporary Ids and Off-Line “Saves”

DevForce typically assigns newly created objects a temporary, primary key “Id”. The objects retain this temporary

id until they are saved to the data store. DevForce keeps a list of these temporary ids.

We can still reference these new objects in other objects. For example, if we create a new order and new order line

items that attach to them, those line items have references to their parent order – references that make use of the

temporary id.

Just before it saves objects to the data store, DevForce fixes up all temporary Ids – including all references to these

ids – by replacing the temporary values with permanent values. The fix-up process relies upon that list of temporary

ids; it forms the basis of the fix-up map that marries a permanent id value to each temporary id value.

What can we do when the user pushes the save button while the application is disconnected? We can‟t reach the data

store so we can‟t acquire permanent ids. Without permanent ids, we can‟t fix up the temporary ids.

We could tell the user to wait until we can reconnect. That‟s not a good experience if the application will be off-line

for more than a few seconds.

Page 491: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

491 | P a g e

The obvious answer is to save the new and changed entities to local disk with SaveCacheState. We can safely

shut down the application if we have to, then re-launch and restore the cache later with RestoreCacheState.

If DevForce only saved entities, we would be unable to fix-up the temporary ids when we restored. We need the list

of temporary ids to do the fix-up. Fortunately, DevForce saves the list of temporary ids with the saved EntitySet.

It has always done so.

However, it used to be that you had only one shot at temporary id list restoration. Either the target cache or the

EntityCacheState could have temporary ids but not both. More importantly, we could restore only from a single

saved EntityCacheState. We were unable to restore multiple CacheStates if more than one of them had

temporary ids.

Why might we need to save and restore multiple CacheStates? Follow along:

1. We start the application and get some objects.

2. We go off-line.

3. We press [save]; the application stores the pending added and changed entities to EntitySetSave1.

4. The application “pretends” that these items have been saved by marking them as unmodified in the cache.

Note that such “unmodified” data may have temporary ids.

5. We make more changes (perhaps even to entities we created earlier) and save again; the app stores locally

to EntitySetSave2.

6. We make more changes.

7. We decide to shut down but we don‟t want to commit these changes so we do not save.

8. The application saves all unmodified data to disk in EntitySetUnmodified.

9. The application saves all pending added and changed entities to EntitySetMods.

10. The application shuts down.

11. We re-launch a few hours later. The application can connect to the data store and is ready to save our

changes.

12. The application restores EntitySetUnmodified to the entity cache.

13. It restores EntitySetSave1 and restores their temporary ids in the process. The restoration overwrites

objects in the cache, refashioning some unchanged entities as either modified or added.

14. It saves these entities, performing the id fix-up first.

15. It erases the now expired EntitySetSave1.

16. Next it restores EntitySetSave2 and its temporary ids.

17. It saves this second batch of entities and erases the file as before.

18. Finally, it restores the EntitySetMods.

19. It does not save these changes because we did not commit them.

The application has now restored the entity cache as it was when we shut down with the important difference that

our saves have been committed to the permanent store.

Page 492: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

492 | P a g e

Securing Offline Data

Saving your Entity Manager cache to an unencrypted local disk file on your laptop creates an obvious security

vulnerability for your data. Fortunately, it‟s easy to encrypt the file.

Saving the Cache Contents to an Encrypted Local Disk File

To encrypt the contents of the cache while store, you use an overload of SaveCacheState () that write the cache to a

MemoryStream:

Page 493: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

493 | P a g e

C#

private void StoreOffLine() {

MemoryStream aMemoryStream = new MemoryStream();

string binaryCacheDataAsString = null;

string encryptedBinaryCacheDataAsString = null;

string filePath = null;

string folderPath = System.Windows.Forms.Application.UserAppDataPath;

if (! (IsValidUserAppDataPath(folderPath))) {

MessageBox.Show("Unable to write data to folder " + folderPath + ".");

return;

}

else {

filePath = folderPath + "\\DevForceCache.bin";

StreamWriter aStreamWriter = new StreamWriter(filePath, false);

try {

_entityMgr.CacheStateManager.SaveCacheState(aMemoryStream, true);

binaryCacheDataAsString = Convert.ToBase64String(aMemoryStream.GetBuffer());

encryptedBinaryCacheDataAsString =

IdeaBlade.Util.CryptoFns.SimpleDESEncrypt(

binaryCacheDataAsString, mEncryptionKey);

aStreamWriter.Write(encryptedBinaryCacheDataAsString);

}

catch (Exception ex) {

MessageBox.Show(ex.Message);

}

finally {

aStreamWriter.Flush();

aStreamWriter.Close();

string msg =

"Local cache contents saved in encrypted form to local storage at: ";

MessageBox.Show(msg + filePath);

}

}

}

#endregion

private bool IsValidUserAppDataPath(object pFolderPath) {

bool retVal = false;

try {

if (! (Directory.Exists(pFolderPath))) {

Directory.CreateDirectory(pFolderPath);

}

retVal = true;

}

catch (Exception ex) {

MessageBox.Show(ex.Message);

retVal = false;

}

return retVal;

}

Page 494: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

494 | P a g e

VB

Private Sub StoreOffLine()

Dim aMemoryStream As New MemoryStream

Dim binaryCacheDataAsString As String

Dim encryptedBinaryCacheDataAsString As String

Dim filePath As String

Dim folderPath As String = System.Windows.Forms.Application.UserAppDataPath

If Not IsValidUserAppDataPath(folderPath) Then

MessageBox.Show("Unable to write data to folder " + folderPath + ".")

Return

Else

filePath = folderPath + "\DevForceCache.bin"

Dim aStreamWriter As New StreamWriter(filePath, False)

Try

_entityMgr.CacheStateManager.SaveCacheState(aMemoryStream, True)

binaryCacheDataAsString = Convert.ToBase64String(aMemoryStream.GetBuffer())

encryptedBinaryCacheDataAsString = _

IdeaBlade.Util.CryptoFns.SimpleDESEncrypt(binaryCacheDataAsString,

mEncryptionKey)

aStreamWriter.Write(encryptedBinaryCacheDataAsString)

Catch ex As Exception

MessageBox.Show(ex.Message)

Finally

aStreamWriter.Flush()

aStreamWriter.Close()

Dim msg As String = _

"Local cache contents saved in encrypted form to local storage at: "

MessageBox.Show(msg + filePath)

End Try

End If

End Sub

#end Region

Private Function IsValidUserAppDataPath(ByVal pFolderPath) As Boolean

Dim retVal As Boolean

Try

If Not Directory.Exists(pFolderPath) Then

Directory.CreateDirectory(pFolderPath)

End If

retVal = True

Catch ex As Exception

MessageBox.Show(ex.Message)

retVal = False

End Try

Return retVal

End Function

The method directs the SaveCacheState () method to write to a memory stream; converts that memory stream to a

Base64String; encrypts the Base64String using an encryption function, SimpleDESEncrypt(), from the

IdeaBlade.Util.CryptoFns namespace; and writes the encrypted string to disk.

Whence the Encryption Key?

In the above code, an encryption key is stored in a class-scoped variable, mEncryptionKey. Where did the value

for that variable come from?

That‟s up to you, but it is much better if it isn‟t stored anywhere, as such. One good scheme is to use the current

user‟s password as the encryption key. If the user can log in (to Windows, or your app, whichever applies), she can

get access to the decrypted data. Otherwise, not.

Retrieving the Cache Contents from an Encrypted Local Disk File

Here is the corresponding code to read back the encrypted EntityCacheState file:

Page 495: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

495 | P a g e

C#

private void LoadOffLine() {

MemoryStream aMemoryStream = null;

string encryptedBinaryCacheDataAsString = null;

byte[] binaryCacheDataAsString = null;

string path = System.Windows.Forms.Application.UserAppDataPath + "\\";

try {

//Read the file

StreamReader aStreamReader = new StreamReader(path + "MyLocalData.bin");

try {

//Convert the encrypted data to a string

encryptedBinaryCacheDataAsString = aStreamReader.ReadToEnd();

//Decrypt the encrypted string

binaryCacheDataAsString =

Convert.FromBase64String(IdeaBlade.Util.CryptoFns.SimpleDESDecrypt(

encryptedBinaryCacheDataAsString, mEncryptionKey));

//Read the decrypted string into a MemoryStream

aMemoryStream = new MemoryStream(binaryCacheDataAsString);

//Restore the EntitySet from the decrypted MemoryStream

_entityMgr.CacheStateManager.RestoreCacheState(

aMemoryStream, RestoreStrategy.Normal, true);

}

catch (Exception ex) {

MessageBox.Show(ex.Message);

}

finally {

//Close the reader and reload the Employee EntityList

aStreamReader.Close();

mEmployees = _entityMgr. Employees;

this.mEmployeesBS.DataSource = mEmployees;

this.mManagersBS.DataSource = mEmployees;

}

}

catch (Exception pException) {

MessageBox.Show(pException.Message);

}

}

Page 496: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Disconnected Applications

496 | P a g e

VB

Private Sub LoadOffLine()

Dim aMemoryStream As MemoryStream

Dim encryptedBinaryCacheDataAsString As String

Dim binaryCacheDataAsString As Byte()

Dim path As String = System.Windows.Forms.Application.UserAppDataPath & "\"

Try

'Read the file

Dim aStreamReader As New StreamReader(path & "MyLocalData.bin")

Try

'Convert the encrypted data to a string

encryptedBinaryCacheDataAsString = aStreamReader.ReadToEnd()

'Decrypt the encrypted string

binaryCacheDataAsString = _

Convert.FromBase64String(IdeaBlade.Util.CryptoFns.SimpleDESDecrypt( _

encryptedBinaryCacheDataAsString, mEncryptionKey))

'Read the decrypted string into a MemoryStream

aMemoryStream = New MemoryStream(binaryCacheDataAsString)

'Restore the EntitySet from the decrypted MemoryStream

_entityMgr.CacheStateManager.RestoreCacheState(

aMemoryStream, RestoreStrategy.Normal, True)

Catch ex As Exception

MessageBox.Show(ex.Message)

Finally

'Close the reader and reload the Employee EntityList

aStreamReader.Close()

mEmployees = _entityMgr.Employees

Me.mEmployeesBS.DataSource = mEmployees

Me.mManagersBS.DataSource = mEmployees

End Try

Catch pException As Exception

MessageBox.Show(pException.Message)

End Try

End Sub

The above method assigns the encrypted file to a StreamReader; reads the encrypted stream into an encrypted string;

decrypts the encrypted string using the IdeaBlade.Util.CryptoFns.SimpleDESDecrypt() method; assigns the

decrypted string to a MemoryStream; and calls RestoreCacheState, passing it the MemoryStream.

Re-synchronizing changes

When the application obtains a server connection, it can synchronize local objects with the remote data source. It

can save local pending changes, relying upon DevForce optimistic concurrency checking to prevent overwriting

other users‟ changes. It might refresh local copies of business objects with objects updated by other users previously.

Login while offline

When logging in while offline, there is no host to authenticate the user. That can still be okay, since of course there

will be no access to the database or other non-local datasource in that circumstance. If you are using the user‟s

password as the encryption key, then the login will constitute the credential that permits the user to see even the

locally stored data.

You may wish to keep the user credentials around for scenarios in which a session begins offline but becomes

connected when connection is again possible. In that circumstance you should keep a hash of the password, rather

than the password inself, in memory (to prevent any rogue module from accessing the password in memory). The

hashed password can be the encryption key for the locally stored EntitySet.

Page 497: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Security

497 | P a g e

Security

Chapter 14 Security Authentication Authorization Encryption ASP.NET Security Integration

DevForce has implemented security protocols that ensure confidentiality and data integrity.

These protocols fall into three categories, all them necessary for comprehensive security.

Authentication Verifies the identity of every participant in the system

Authorization Role-based security restricts access and update to data based on permissions granted to a

logged-in application user.

Encryption Prevents unauthorized third parties from eavesdropping on a connection.

Authentication

There are two sides to the authentication equation: the client must authenticate the server and the server must

authenticate the client.

User Identity Verification - Login

The client must log into a DevForce Business Object Server before it will deliver any Entity Services.

DevForce provides a login mechanism that builds on the developer‟s implementation of .NET's IPrincipal

interface. That interface is robust and supportive of a wide range of application login schemes including Windows

authentication and LDAP. ASP.NET authentication is also specifically supported by DevForce.

After login, the client and server exchanges are accompanied by a tamper-proof certification called a

SessionBundle. The server insists on seeing it with every client request.

The server typically caches the SessionBundle for performance. However, the cached version is not essential and

can be (re)constructed as needed so that load-balanced, multi-server applications can be truly stateless and do not

have to be session-aware.

Client-side, your code can access the authenticated IPrincipal through the EntityManager.Principal property.

Implementing IEntityLoginManager

Verifying a user‟s identity can be easily accomplished using the IEntityLoginManager interface. Add a class which

implements the interface methods, and ensure that DevForce can find the class by placing the name of the assembly

in which it‟s defined in the top level ProbeAssemblyNames in the IdeaBlade section of the configuration file. Note

that the assembly must be as a top-level one: DevForce does not probe for this interface by looking at the EdmKeys,

Page 498: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Security

498 | P a g e

since handling login is not datasource-specific. The assembly needs to be deployed only on the server, since login

processing in an n-tier application is not performed on the client.

The interface contains two methods: Login() and Logout().

Login()

Your Login method will be passed the ILoginCredential that the client used in the EntityManager.Login() call. Note

that if you call EntityManager.Login() without credentials, or allow an implicit login to take place, that the

credential will be null: your code should handle this.

An EntityManager is also passed to the method to allow you to easily query your domain model. The

EntityManager here is a special, server-side EntityManager instance which is already “connected” to the

EntityService and does not require a login. You can execute queries from this EntityManager if necessary. This

EntityManager is not an instance of your sub-typed DomainModelEntityManager, but rather of the base

EntityManager class. You can, however, still easily create queries. For example:

C#

// Build an EntityQuery from scratch.

var query1 = new EntityQuery<IIdentity>("Customers", entityManager);

// Construct a domain-specifc EntityManager and use its query methods.

DomainModelEntityManager em = new DomainModelEntityManager(entityManager);

var query2 = em.Customers;

From your Login() method, you should return a type implementing IPrincipal. Common implementations are

GenericPrincipal, WindowsPrincipal, or UserBase (in Silverlight applications); but any serializable type is allowed.

If the credentials supplied are not valid, you should throw a LoginException indicating the cause of the failure. On

the server, DevForce will cache the IPrincipal that is returned, and will also encrypt it in the token it returns to the

client. The client can retrieve the IPrincipal using the EntityManager.Principal property. On both client and server,

the IPrincipal can be used for subsequent user authorization checking.

Logout()

The Logout method allows you to perform any processing needed when the user logs off. You might find this useful

to perform session-level auditing or resource cleanup. Even if you have no need for logout processing, you must

still implement an empty method.

Sample

Here‟s a sample class implementing the IEntityLoginManager interface. It returns a GenericPrincipal from the

Login() method, and requires no special Logout processing.

Page 499: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Security

499 | P a g e

C#

public class LoginManager : IEntityLoginManager {

public IPrincipal Login(ILoginCredential credential, EntityManager entityManager) {

// Disallow null credentials and throw a LoginException

if (credential == null) {

throw new LoginException(

LoginExceptionType.NoCredentials, "Credentials not supplied");

}

// Build a simple Principal/Identity. You may return any serializable

// types implementing IPrincipal.

GenericIdentity identity = new GenericIdentity(credential.UserName);

GenericPrincipal principal = new GenericPrincipal(identity, new string[] { });

return principal;

}

public void Logout(IPrincipal principal, EntityManager entityManager) {

// No special processing needed.

}

}

Customizing the Login process

ILoginCredential. Any serializable type implementing the ILoginCredential interface can be used. The

LoginCredential, and the FormsAuthenticationLoginCredential for Silverlight applications, are the DevForce-

supplied implementations. The credential supplied in the EntityManager.Login() call is the credential received in

the IEntityLoginManager.Login(). The class defining the ILoginCredential must be available on both client and

server, and must be discoverable by DevForce.

IPrincipal. Any serializable type implementing System.Security.Principal.IPrincipal can be returned from the

Login() method. The object is returned to the client and is available via the EntityManager.Principal property on

the EntityManager instance on which Login was called. The IPrincipal is also available to other server methods.

The class defining the IPrincipal must be available on both client

and server and discoverable by DevForce.

LoginException. Any serializable type extending

LoginException can be thrown for login failures. To ensure that

your custom exception is received correctly on the client, you

must also implement a constructor accepting the message, and a

constructor accepting a dictionary of any custom properties.

DevForce will automatically serialize any custom properties via

a Dictionary<string, object>, and will call the appropriate

constructor when building the exception on the client. The class

defining the custom exception must be available on both client

and server and discoverable by DevForce. For example:

Discoverability

In a standard WinClient application,

discoverability is generally not an issue.

If you place your types in assemblies also

listed in the ProbeAssemblyNames in the

IdeaBlade Configuration, DevForce will

find and use the type.

In a Silverlight application, on the other

hand, you need to go an extra mile. You

must either have the type implement the

IKnownType interface, or adorn the type

with the DiscoverableType attribute.

Page 500: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Security

500 | P a g e

C#

[Serializable]@public class CustomLoginException : LoginException {

public CustomLoginException(string message, int severity) : base(message) {

Severity = severity;

}

public CustomLoginException(string message,

Dictionary<string, object> userData) : base(message) {

Severity = (int)userData["Severity"];

}

public int Severity {

get;

private set;

}

}

Ensuring That Login Will Fail

If an IEntityLoginManager Class Is Not Deployed

By default in DevForce, login succeeds when the application is launched while disconnected, or if DevForce can‟t

find the application‟s implementation of the IEntityLoginManager interface. This is so even if the user enters

incorrect credentials.

This behavior is by design. We want to make it easy for the developer to move forward with application

development without worrying about security features in the early going. In production use, on the other hand, a

security hole is created if an IEntityLoginManager class is expected by the application, but somehow has not been

properly deployed. Absent other countermeasures, this could result in successful logins by unauthorized users.

There is a property in the IdeaBlade Configuration File called LoginManagerRequired. (The property derives

from the <loginManagerRequired> tag under the XML root in the IdeaBlade configuration section of

the app.config XML file.) LoginManagerRequired defaults to false, preserving the loose checking that is

desirable during development. If, on the other hand, the property has been set to True and the EntityServer

cannot find an implementation of IEntityLoginManager, the EntityServer throws a LoginException of

type LoginExceptionType.NoLoginManager. This logic applies whether the EntityServer is executing on

the client (as it is in 2-tier or offline mode) or on the Business Object Server.

If your application expects an IEntityLoginManager class to authenticate logins, you should take care in

any production version to set the LoginManagerRequired property to True in the configuration file

(app.config or web.config). This will serve as a second line of defense against any failure to properly

deploy the IEntityLoginManager class.

Authorization

In a truly distributed environment, where business logic can operate in an insecure client side environment, it is

critical to guard against a compromised client so that it cannot damage data or perform restricted operations.

The DevForce platform provides two such mechanisms that operate on the server-side.

Server side role-based security

DevForce supports both declarative and programmatic role-based authorization of server-side methods. Most

server-side interface implementations, such as IEntityServerFetching, IEntityServerFetched, IEntityServerSaving,

and IEntityServerSaved, and remotely-invoked methods called via EntityManager.InvokeServerMethod(), receive an

IPrincipal in their input arguments. This IPrincipal is the one returned from the IEntityLoginManager.Login() call,

Page 501: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Security

501 | P a g e

and represents the current user making the request. When not explicitly passed into the method or property, the

System.Threading.Thread.CurrentPrincipal can also be retrieved; it, too, represents the current logged-in user

making the request.

In a Silverlight or ASP.NET application, the HttpContext.Current.User can also be used if the

aspNetCompatibilityEnabled flag has been set.

Programmatic Authorization

In any server-side method or property to which access must be restricted or authorized, you can add some simple

code to check the user‟s identity. If the user is not authorized for the action, then you can cancel it (if fetching or

saving) or throw an exception.

For example, here‟s a simple class which authorizes all fetches and saves, via implementation of the

IEntityServerFetching and IEntityServerSaving interfaces:

C#

public class EntityServerEventsManager : IEntityServerSaving, IEntityServerFetching {

public void OnSaving(EntityServerSavingEventArgs e) {

// Cancel the save if user is not in the "secureuser" role.

if (!e.Principal.IsInRole("secureuser")) { e.Cancel = true; }

}

public void OnFetching(EntityServerFetchingEventArgs e) {

// Cancel the fetch if user is not authenticated.

if (!e.Principal.Identity.IsAuthenticated) { e.Cancel = true; }

}

}

Authorization Attributes

Declarative authorization can also be used to decorate server-side methods with authorization attributes. DevForce

will perform an authorization check for any IEntityServerFetching, IEntityServerFetched, IEntityServerSaving and

IEntityServerSaved implementation, any remotely-invoked methods called via the

EntityManager.InvokeServerMethod, and any query methods used with POCO types. The RequiresAuthentication

and RequiresRoles attributes, or any custom attribute derived from these (or from the AuthorizationAttribute), will

be recognized and used to authorize the current logged-in user before the method is invoked.

Note that these attributes may not be used with properties.

Here‟s a class functionality identical to the one shown above, but coded using declarative security:

C#

public class EntityServerEventsManager : IEntityServerSaving, IEntityServerFetching {

[RequiresRoles("secureuser")]

public void OnSaving(EntityServerSavingEventArgs e) { }

[RequiresAuthentication]

public void OnFetching(EntityServerFetchingEventArgs e) { }

}

Page 502: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Security

502 | P a g e

Note that, in the cases above, you may not cancel the action when using declarative security; and that more complex

logic (such as inspecting the element type of the query or the objects to be saved) cannot be performed. A

PersistenceSecurityException is thrown by DevForce if the user is not authorized for the action.

Encryption

Security against eavesdropping is provided by encasing all communications using a secure protocol such as HTTPS

/ SSL. If more security is required, communications can be double-encrypted using a high-grade system such as

AES or Triple-DES.

ASP.NET Security Integration

You can use ASP.NET security features (Membership, Roles, Profile) in a DevForce application. Integration with

these features is available by default in a Silverlight application, but may also be used in ASP.NET and WinClient

applications.

See http://www.asp.net/learn/security/ for information on ASP.NET security features.

Setup

The useAspNetServices flag in ideablade.configuration controls whether DevForce will use ASP.NET security

features. This flag is applicable to server-side operations only, and will generally be set in the web.config file.146

In

a Silverlight application, one in which the clientApplicationType is "Silverlight", this flag is true by default. When

enabled, DevForce will use the AspNetAuthenticatingLoginManager to handle login processing from clients.

XML

<ideablade.configuration version="5.00" useAspNetServices="true" ... >

You must also enable AspNetCompatibility for the DevForce services in order to integrate with ASP.NET services.

You set this in the system.serviceModel configuration section. (You must use the serviceModel section to configure

the DevForce services to take advantage of this feature.) Here's the relevant element in the system.serviceModel

section:

XML

<system.serviceModel>

<!-- Set this to true to allow the BOS to function within the ASP.NET HTTP pipeline and

use ASP.NET services. -->

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

</system.serviceModel>

You must enable the ASP.NET services you wish to use in the system.web configuration section of the config file, as

well as choose the type of authentication wanted. These steps are described below.

Authentication

Authentication (validating the user‟s identity) can take either of two flavors – Forms or Windows.

146 You would, of course, use a standard app.config if using ASP.NET integration in a WinClient application.

Page 503: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Security

503 | P a g e

Forms Authentication

To use Forms authentication, do the following:

1. Set authentication mode="Forms" in the config file. The ASP.NET membership service is enabled by

default.

XML

<system.web>

<authentication mode="Forms" />

</system.web>

2. Pass login credentials in the Login (LoginAsync) call:

C#

var mgr = DomainModelEntityManager.DefaultManager;

LoginCredentials cred = new LoginCredentials("user", "pwd", "domain");

mgr.LoginAsync(cred, LoginCallback, null);

//-- or --

mgr.Login(cred);

(Note the DomainModelEntityManager.DefaultManager is used as an example only. The Login call can be made

on any EntityManager instance.)

DevForce will process this login and validate the credentials with the ASP.NET membership provider. If the user is

authenticated, a FormsAuthenticationTicket is issued. If you want the ticket to be persistent you should pass a

FormsAuthenticationLoginCredential in the Login() call, since this credential allows you to set the persistence flag.

Windows Authentication

To use Windows authentication, do the following:

1. Set authentication mode="Windows" in the config file. Note that Windows authentication is best suited

to applications running within an organization's intranet.

XML <system.web>

<authentication mode="Windows" />

</system.web>

2. You still need to perform a Login (LoginAsync) call, but you should not pass login credentials. (DevForce

uses the SessionBundle returned from a Login as a token with all subsequent messages. In WinClient

applications, DevForce will perform an implicit Login with null credentials if you do not explicitly call

Login. In Silverlight applications, DevForce will not perform this implicit login processing, and you must

always call LoginAsync.)

C# var mgr = DomainModelEntityManager.DefaultManager;

mgr.LoginAsync(null, LoginCallback, null);

// -- or --

mgr.Login(cred);

Page 504: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Security

504 | P a g e

When Windows authentication is used, ASP.NET sets the HttpContext.Current.user to a WindowsPrincipal

representing the user. DevForce will use this user as the logged in user and create a UserBase from the information

provided.

For either type of authentication, after Login completes a UserBase instance representing the user is available on

both client and server.

On the client, access this using the Principal property on the EntityManager:

C#

var mgr = DomainModelEntityManager.DefaultManager;

UserBase currentUser = mgr.Principal;

(Note the DomainModelEntityManager.DefaultManager is used as an example only. The Principal is available from

whatever EntityManager performed the Login.)

On the server, the UserBase will be passed into any method which takes an IPrincipal parameter. The

Thread.CurrentPrincipal will also return the UserBase.

Roles

You must enable the Role service in the configuration file in order to use this feature:

XML

<system.web>

<!-- Set to enable/disable the ASP.NET Role Manager. -->

<roleManager enabled="true" />

</system.web>

With roles enabled, user role information will be obtained from the ASP.NET RoleProvider, and role-based

authorization can be used in your application. Use UserBase.Roles to retrieve all roles for the user, and

UserBase.IsInRole() to determine role membership.

Check the ASP.NET documentation for information on how to create and manage roles and assign users to roles.

Profile

You must enable the Profile service in the configuration file in order to use this feature:

XML

<system.web>

<!-- Set to enable and configure the ASP.NET Profile. These are sample

properties. -->

<profile enabled="true">

<properties>

<add name="WindowSeat" type="bool" defaultValue="false" />

<add name="Building" type="string" defaultValue="A" />

</properties>

</profile>

</system.web>

Page 505: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Security

505 | P a g e

You also need to extend the UserBase class with the custom properties from your profile. DevForce will

automatically populate these properties from the Profile if the property name and type match, and the setter is

public. Your custom UserBase class must be serializable, and implement IKnownType or be marked with the

DiscoverableType attribute. Your custom type will be available on both the client and server wherever you would

normally expect to receive a UserBase. A sample application using the Profile service is available with the Learning

Resources installed with DevForce.

Here‟s a sample class based on the profile properties used above:

C#

[DataContract]

public class CustomUser : UserBase, IKnownType {

public CustomUser(IIdentity identity, IEnumerable<string> roles) :

base(identity, roles) { }

// Custom properties to be loaded from Profile.

[DataMember]

public bool WindowSeat { get; set; }

[DataMember]

public string Building { get; set; }

}

Customizations

Credentials (Forms authentication)

You can pass custom credentials, derived from ILoginCredential, LoginCredential, or

FormsAuthenticationLoginCredential with the Login (LoginAsync) call. With custom credentials, you will

generally also want to provide a custom IEntityLoginManager implementation to receive these credentials. If you

wish to take advantage of existing DevForce ASP.NET service integration, you should derive your class from the

AspNetAuthenticatingLoginManager and override methods as needed.

IEntityLoginManager

You can implement your own IEntityLoginManager or extend the AspNetAuthenticatingLoginManager to provide

custom logic. Any custom implementation will be used if found.

UserBase

You can also extend the UserBase class. If you enable the ASP.NET Profile service you will want to use a custom

UserBase which contains additional properties retrieved from the profile. DevForce will automatically return your

custom UserBase (if found) without the need to implement a custom AspNetAuthenticatingLoginManager.

Special situations

If you are already using ASP.NET security features in your application and want DevForce to recognize the current

user identity, call Login (LoginAsync) with null credentials. DevForce will use the HttpContext.Current.User as its

logged in user also. Be sure to perform the setup tasks listed above so that DevForce can integrate with ASP.NET

services.

Opting Out

By default, a Silverlight application will integrate with ASP.NET services if the useAspNetServices and

aspNetCompatibilityEnabled flags described in the Setup section above are set to true and you have not

Page 506: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Security

506 | P a g e

implemented a custom IEntityLoginManager. At this time the useAspNetServices flag cannot be turned off in the

ideablade.configuration, but you can set this flag in the Application_Start logic in Global.asax or in a custom

EntityServiceApplication. The easiest way to turn off ASP.NET integration is to turn off the

aspNetCompatibilityEnabled flag (or remove the serviceHostingEnvironment element entirely).

Troubleshooting

"Error using ASP.NET Membership: Unable to connect to SQL Server database." Message received on a Login

(LoginAsync) call.

This will occur if the ASP.NET membership database cannot be found or opened. You must configure the

ASP.NET membership provider if you wish to use ASP.NET security features, and by default the

AspNetSqlProvider is used. This will use the LocalSqlServer connection string from either your web.config or

the machine.config. The default connection expects a SQLExpress database named aspnetdb.mdf. For more

information on configuring ASP.NET membership see the membership tutorials at

http://www.asp.net/learn/security/ .

The default in the machine.config:

XML

<connectionStrings>

<add name="LocalSqlServer" connectionString="data

source=.\SQLEXPRESS;Integrated

Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User

Instance=true" providerName="System.Data.SqlClient"/>

</connectionStrings>

To override in your web.config (to the default instance of the local SQL Server):

XML

<connectionStrings>

<remove name="LocalSqlServer" />

<add name="LocalSqlServer" connectionString="Data

Source=.;Initial Catalog=aspnetdb;Integrated Security=True;"

providerName="System.Data.SqlClient" />

</connectionStrings>

Deployment

Deployment

Document Overview

DevForce And the App.Config File Creating and Editing a Configuration File IdeaBlade DevForce Configuration Editor DevForce Elements in App.Config Configuration File Location Client and Server Versions of App.Config

Page 507: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

507 | P a g e

Probing in DevForce

Data Server Deployment

Deploying a DevForce Silverlight Application Deploying to IIS Version 6 Deploying to IIS Version 7 Troubleshooting Resources

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

Document Overview

This document details deployment processes and piece parts. We begin with a discussion of the app.config and

web.config configuration files; briefly discuss data server deployment; and then provide stepwise instructions for

DevForce Silverlight applications and the several flavors of DevForce WinClient application.

All developers should read the sections DevForce And the App.Config File and Data Server Deployment.

Then,

if deploying a Silverlight app, proceed to the section Deploying a Silverlight Application; or,

if deploying a WinClient app, proceed instead to the section Deploying a DevForce WinClient Application.

DevForce And the App.Config File

The application configuration file, app.config, is critical to making your application run properly. The app.config

file contains settings that govern many important aspects of the way a DevForce application runs. Some settings

determine which data sources are reached and how. Some concern aspects of the application‟s deployment.

The application developer enters these parameters in the IdeaBlade section of app.config. This file can be edited

from within the DevForce Configuration Editor, or within Notepad or any XML editor. The DevForce Configuration

Editor provides helpful additional structure, such as presenting elements currently not represented in the file (and

therefore being taken at their default values). It is very helpful if you don‟t remember the exact name or format of a

particular element.

A DevForce-based, data-driven, distributed application requires the setting of numerous parameters. We need two

slightly different versions of this file, one for the client and one for the server.

The client-side version of the file is typically embedded as a resource file in the executable itself. It may also be

deployed loose in the application‟s executables directory; but in the case of the client-side app.config, we strongly

recommend against that. It is an invitation to tampering, and there is some danger that a user might erase it or lose it

while shuffling files on his local disk.

A strategy that many developers find serviceable is to maintain the configuration file, during initial development, in

the domain model project where it is placed by default. When and if the application is deployed n-tier, make a copy

of the app.config, modify it as appropriate for its role on the server, and maintain it there – behind the firewall – as a

loose file. Then modify the version in the domain model project to make it suitable for the client configuration role

-- removing connection strings, etc. – and continue maintaining it in the domain model project. The domain model

Page 508: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

508 | P a g e

assembly will be deployed on both sides of the firewall, but the app.config file in the server copy of the domain

model assembly will be trumped by the loose copy of app.config that you place there. (DevForce always gives the

loose copy priority.)

Creating and Editing a Configuration File

In our first experience writing a DevForce application, we take little notice of the application configuration file. The

Object Mapper silently adds a default app.config file to the project where it generates the domain model.

This default version of the file is pre-set with values that are appropriate for an application running on the

developer‟s PC without a remote BOS. Among the default values is the database connection string, which is set to

the same string that the Object Mapper uses to access the application‟s design database.

We developers quickly outgrow this version and soon find ourselves editing the app.config file, setting the

parameters that indicate where to find helper assemblies and how to deploy the application.

IdeaBlade DevForce Configuration Editor

App.config is an XML file and, as such, can be edited with Notepad or any XML editor – as you are likely to do

when tweaking a copy on a server where the DevForce software is not installed.

We recommend editing the file with the IdeaBlade Configuration Editor whenever possible. This editor

understands the IdeaBlade Configuration scheme, provides descriptive help in the status area, and can eliminate

many simple errors such as element tag misspelling.

Page 509: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

509 | P a g e

You can use the IdeaBlade Configuration Editor in two different ways:

1. Launch it from the DevForce folder in the Windows Start menu.

2. Configure it to work within Visual Studio. To do this:

a. Select the app.config file, right-click, and select Open With… option.

b. If you do not see ConfigEditor.exe in the list of programs, click the <Add…> button.

c. On the Add Program dialog, click the ellipsis button to browse to a file. Navigate to the IdeaBlade

DevForce installation directory (typically C:\Program Files\IdeaBlade DevForce) and select the

file ConfigEditor.exe. Give it any “Friendly Name” you wish; e.g., “DevForce Configuration

Editor”.

d. Once DevForce Configuration Editor is in the list, double-click it to open the configuration file

in that editor.

Page 510: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

510 | P a g e

DevForce Elements in App.Config

The IdeaBlade section of App.config contains elements governing a wide array of application behaviors. Some of

the elements relate to persistence management, some to debugging, some to deployment, etc.

The IdeaBlade section of app.config consists of attributes and elements. Elements are described below, including

discussion of selected attributes:

Element Description

(Root) Includes attributes such as LoginManagerRequired , an important security

setting; and UseDTC, a flag that determines whether the Microsoft Distributed

Transaction Coordinator is used for transactional saves and queries.

Probe Assembly

Names

The developer may provide custom implementations for any of several kinds of

interfaces. Examples include IEntityLoginManager and

IDataSourceKeyResolver147

. DevForce discovers the custom class that

implements these interfaces by probing for them among assemblies of the

application. In this section, the developer tells DevForce which assemblies to

examine when probing for a class of general applicability148

.

Options “Options” represents a bag of application-wide <option/> tags149

. These are

available to the developer, who may add any number of custom options.

Logging Identify where and how to write the DevForce DebugLog with tags in this area.

EdmKeys Configuration data applicable to one or more Entity Data Model data sources.

There will be several named <edmKey/> group tags if the application uses more

than one Entity Data Model.

Each EdmKey group contains settings that could include the database

connection string and probe assembly names for assemblies that hold auxiliary

classes for id generation.

RdbKeys This element will not be used by most applications. You may need it if your

application uses the legacy AdoHelper object (e.g., in an IidGenerator

implementation).

WsKeys Configuration data applicable to one or more web service data sources. The

<wsKey/> group tags parallel in purpose their <edmKey/> tag cousins.

ObjectServer Configuration data governing access to the DevForce Business Object Server

(BOS) in an n-tier deployment.

The isDistributed attributed must be set to “true” when deployed as n-tier.

The default is “false” – meaning 2-tier or 1-tier –, in which case the other

147 Used to provide application specific authentication and DataSourceKey resolution, respectively.

148 There are also probing paths that are local to the domain of a particular data source. An implementation of a IIdGenerator

that is specific to one database is an example of a class DevForce should probe for among the assemblies named in the local

<probeAssemblyNames/> tags within an EdmKey or WsKey.

149 There are also <option /> tags that are local to the domain of a particular data source.

Page 511: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

511 | P a g e

settings are irrelevant.

Verifiers Under this element you can define verifiers external to your application code.

See the chapter “Validation Through Verification” for more information on

verifiers.

NotificationService Use this element to define settings for the Push notification feature.

Configuration File Location

Shortly after application launch, the DevForce framework looks for the configuration file. The file must be named

“app.config” and located in one of three places. In search order they are:

The same directory as the application executable, as a loose file.

The assembly manifest of the application executable, as an embedded resource file.

An assembly name AppHelper, as an embedded resource file.

These options are now detailed:

1. Loose in the application executable directory

The directory that holds the “.exe” file is the application executable directory.

Just where that is may not be as obvious as it seems at first. Let‟s suppose that we are using Visual Studio,

haven‟t changed its default build configuration patterns, and have selected a particular project as the start-

up project, meaning that this project will produce the executable assembly.

Under such circumstances, C# executables are in a sub-directory of the start-up project – either

\bin\debug (for a debug build) or \bin\release (for a release build) – and VB.NET executables are in

the \bin sub-directory of the start-up project (for both debug and release builds).

If you have added an application configuration file to your Visual Studio project with a Build Action of

None, then when you build your project Visual Studio will automatically rename the app.config file to

<executable name>.exe.config, and copy it to the deployment directory.

2. Embedded as a resource in the executable assembly

If DevForce doesn‟t find the configuration file in the executable‟s directory it looks next for a resource

embedded in the executable.

Embedding the configuration file inside the assembly executable‟s manifest is a practice common with

.NET resource files generally. By carrying it around in the assembly we don‟t have to worry about losing

the file or end users from accidentally deleting this necessary resource. It also deters the mischievous

alterations that loose files invite.

There are three small “gotchas” with embedded resources:

They are embedded, which means we have to recompile the application when we change them.

Visual Studio fails to recognize changes to resource files and so won‟t rebuild without prodding.

If we pick a different project as our start-up assembly, we have to remember to move the

configuration file to the new start-up project.

Page 512: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

512 | P a g e

3. Embedded as a resource in the AppHelper assembly

This location is searched for backward compatibility with older apps created when DevForce generated the

app.config into a project named AppHelper which it created for the purpose. The AppHelper assembly is

the third place the framework looks for app.config.

DevForce won‟t look in the other assemblies of the application. In other words, a configuration file embedded in a

dependent assembly (other than AppHelper) will be ignored.

Client and Server Versions of App.Config

During development, and for purposes of discussion, it is often convenient to consider only a single configuration

file. A single file will suffice even in a few production environments and in 1- or 2-tier WinClient application

scenarios. However, there are good reasons to have one version on the server and a slightly different version on the

client, especially when deploying more than two tiers.150

The best reason is security. Observe that each edmKey contains a database connection string. A connection string

identifies exactly what database is used, where it resides, and how it can be accessed. In many cases, the string

contains a user id and password of a user with wide-ranging database permissions; an expression such as “user

id=sa; password=…” is quite common.

It is trivial to crack open the IdeaBlade Configuration file and extract this gold, even when it is an embedded

resource.

Warning: Disassembling a .NET application is also easy. Don‟t think for a minute that there is a good way

to hide the connection string anywhere on the client-side of a .NET smart-client application.

There is no good reason for the connection string to be present on the client machine of a DevForce remoting

application – one in which the Business Object Server runs on a central host and Business Objects are “remoted” to

the client. The client talks only to the Business Object Server, never the data source directly.

A client executable needs many of the settings in the IdeaBlade Configuration File but it doesn‟t need all of them

and it certainly shouldn‟t have a connection string in a production release.

When a setting is used on both the server and the client, the settings usually should be identical. Yet even here

differences may be permissible and useful; logging details, for example, could be quite different on server and

client.

Accordingly, most production deployments will involve different versions of the configuration file on the server and

on the client.

Change Across Development Environments

A code assembly may be invariant as it progresses from the developer‟s environment to the test, staging, and

production environments. On the other hand, many of its associated application settings are almost certain to be

different in each of those environments.

Consider the database connection string. The developer is likely to have a test database on her own machine. If

developing a WinClient app, she may have only a single configuration file, as yet not split between server and client

versions.

Q/A is likely to split the Winclient configuration file into client and server versions. It will have a different test

database for the build, one with its own case data, and this means a change to the connection string. It will have

150 For Silverlight apps, the server-side configuration file is named web.config, and it will always be present, even when

developing an app entirely on a single computer using Visual Studio and the Microsoft Cassini web server. Web.config is

similar in structure to app.config.

Page 513: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

513 | P a g e

another database (another string) in the staging environment for one final check-out. There may even be a beta

environment with a beta database. When the release is finally deployed, the assemblies will use another connection

string to point to the production database.

In DevForce development it is expected that there will be different configuration files for each environment and that

these files will be managed appropriately in accordance with shop standards and practices.

The DevForce N-Tier Configuration Starter (available on the Windows Start menu for IdeaBlade DevForce, in the

Tools section) performs an initial split on our behalf, for WinClient applications. We can use this as a guide but are

likely to split it ourselves as we begin to refine our deployment plans and get closer to a production release.

Probing in DevForce

Triggered by different types of requests made by your code, explicitly or otherwise, DevForce searches selected

assemblies for types that satisfy certain criteria. The types of items searched for fall into three categories:

Classes that implement particular DevForce interfaces

Classes and members that you have decorated with a DevForce-defined attribute

Classes that extend particular DevForce base types.

We‟ll look at these individually in a moment. But first we want to make a few general comments about probing.

You specify to DevForce that you want a particular assembly to be probed by including the name of that assembly in

a list of probe assemblies in your application‟s configuration file (web.config for Silverlight apps; app.config for

others).

Page 514: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

514 | P a g e

When specifying to DevForce that you want a given assembly to be searched during probing, you must consider

whether the probing of that assembly should be specific to a particular data source key (such as an edmKey,

corresponding to a particular Entity Data Model / EDMX file; or a wsKey, corresponding to a particular web

service). The configuration file contains two different lists. In the sample above, only the second-listed set of probe

assemblies is specific to a particular edmKey (the one named “Default”); assemblies listed in the first set would be

probed as needed regardless of the Entity Data Model being exercised at a given moment.

In many cases you have choice over which list to use; for many purposes, only the assemblies listed in the top-level

list are searched. In tables below we indicate which lists are appropriate for which interfaces, attributes, or base

types.

Probed DevForce Interfaces

The following table lists DevForce interfaces for implementations of which DevForce probes under appropriate

conditions. Assemblies containing such implementations must be listed as probe assemblies in your app.config or

web.config file. The column Top-Level or edmKey in the table shows you your options for where to list a given

assembly.

For assemblies which may be listed as either DataSourceKey-specific or Top-Level probe assemblies, the

DataSourceKey-specific listing will take precedence. If you have implemented an interface more than once -- once

in an assembly listed in the DataSourceKey-specific probe assemblies and once in an assembly listed in the top-level

probe assemblies -- the implementation in the DataSourceKey-specific probe assembly will be found first, and used.

Interface Top-Level or DataSourceKey-Specific

Client- or Server-Side Probing

IConcurrencyStrategy edmKey + Top-Level Server

IDataSourceKeyResolver Top-Level Client and Server

IEntityLoginManager Top-Level Server

IEntityServerFetched Top-Level Server

IEntityServerFetching Top-Level Server

IEntityServerSaved Top-Level Server

IEntityServerSaving Top-Level Server

IIdGenerator edmKey + Top-Level Client and Server

IKnownType All probe assemblies searched, since DevForce is looking for all implementations of this interface.

Client and Server

IWsProxyInterceptor wsKey Server

Probed DevForce Attributes

Page 515: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

515 | P a g e

Attribute Top-Level or DataSourceKey-Specific

Client- or Server-Side Probing

EnableClientAccessAttribute Top-Level Server

DiscoverableTypeModeAttribute – ServerService Top-Level Server

DiscoverableTypeModeAttribute - KnownType any Client and Server

DevForce Attributes Discovered Using Reflection

DevForce uses this attribute as confirmation that a method specified in an client-side InvokeServerMethod call may be executed.

Attribute

AllowRpcAttribute

DiscoverableMemberAttribute - AllowRSM

Probed DevForce Base Types

Base Type Top-Level or DataSourceKey-Specific

Client- or Server-Side Probing

EntityServiceApplication Top-Level Server

Developer-Implementable DevForce Interfaces That Aren‟t Probed

Interface Name

ILoginCredential

What To Do If Your Implementation Isn‟t Found

In most cases this results from listing the assembly in the wrong set of probe assemblies; i.e., listing among the DataSourceKey-specific probe assemblies an implementation that belongs in an assembly listed among the top-level assemblies.

Page 516: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

516 | P a g e

What Is An Assembly Name? Must It Be Fully Qualified?

In non-Silverlight applications, the name DevForce is looking for is the assembly simple name; i.e., the assembly file name less the “.DLL” or “.EXE”extension. For example, for the DomainModel.dll assembly, DevForce expects “DomainModel”.

In Silverlight applications, the assembly display name should be used in the probe assembly lists.

This display name should include the assembly simple name (defined just above), version,

culture and public key token.

For example, the assembly display name for the DomainModel.dll assembly might look like the

following:

DomainModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

(The public key will be non-null if you have signed the DomainModel assembly.) The version number is the assembly version defined in your code.

Automatic Updating of DataSourceKey-Specifc Probe Assembly Lists

When you save your work in the DevForce Object Mapper, and also when you build your application (because a DevForce build watcher program executes), DevForce will add and/or update the DataSourceKey-specific probe assembly lists to include the assembly(ies) that contain the DomainModel and Entity Data Model(s). It will use the form of the assembly name appropriate to the type of application (Silverlight or non-Silverlight).

Data Server Deployment

The data server must be accessible from the Application Server or WinClient application, and run a supported data

source.

Relational Database Management (RDBM) systems are the most common enterprise application data source.

DevForce supports all data sources supported by the Microsoft Entity Framework.

You should know your RDBM system well. You are responsible for the correctness of database connection strings

and proper setting of the database configuration options that control performance and special behaviors (e.g.,

transaction isolation levels).

The DevForce Business Object Server (BOS) connects to each database, via the Entity Framework, with a single

“user id”. You must grant that user the permissions it needs to perform the operations your application requires.

In an n-tier deployment (including all Silverlight deployments), the client application never connects directly to the

data source; its access is always mediated through the BOS. We need not and should not expose the connection

string on the client.

Deploying a DevForce Silverlight Application

Select the appropriate section below based on the version of Internet Information Services running on your target

web server:

Page 517: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

517 | P a g e

Deploying to IIS Version 6

Deploying to IIS Version 7

Deploying to IIS Version 6

1. Ensure that the XAP mime type is registered in IIS. The server must recognize the .XAP extension in order

to serve a Silverlight application. See http://learn.iis.net/page.aspx/262/silverlight/ for more information on

registering the mime type for different IIS versions.

2. The DevForce BOS runs as a WCF service, so you must also ensure that WCF is registered with IIS. See

the topic, “Ensure That IIS and WCF Are Correctly Installed and Registered” in the document at this URL:

http://msdn.microsoft.com/en-us/library/aa751792.aspx. Note that you do not need to create service files or

configure endpoints – these are provided for you in the .svc files and in the DevForce-generated web.config

(or the samples provided).

3. On the web server, create a physical directory for your application under the web site root folder (typically

c:\ inetpub\wwwroot). (The name used for the application in its URL can be different from this folder

name.)

4. Create bin, ClientBin, and log folders under the above directory. Change the NTFS permissions on the log

folder to give the Users group Modify permission. (This will allow the debug log to be written into this

folder.)

5. Create a virtual directory (or choose an existing one) in IIS. You can do this using the Internet Information

Services Manager. Give the virtual directory Read and Run scripts permissions.

The web site will normally host both the Silverlight application (the .html, .aspx, and .xap files) and the

DevForce BOS. (Hosting the BOS at a different site as the Silverlight application requires additional setup

steps not covered here, including the provision of a cross-domain policy file.)

6. While still in the Internet Information Services Manager, browse to the application‟s log folder, right-click

it, select Properties, and remove Read access to the folder. This will prevent non-authorized persons from

examining the application’s debug log.

7. Make any necessary changes to the app.config file in your Silverlight project. For example, we might

change the objectServer section from this (appropriate for development on a single machine:

XML

<objectServer isDistributed="true"

remoteBaseURL="http://localhost "

serverPort="9009"

serviceName=" EntityService.svc"

/>

to this:

XML

<objectServer isDistributed="true"

remoteBaseURL="http://www.ideablade.com"

Page 518: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

518 | P a g e

serverPort="80"

serviceName="SilverlightConsole/EntityService.svc"

/>

8. If deploying to production, turn off any diagnostics or debugging settings in your web.config. Specifically,

Make sure the debug attribute of the <compilation> element in the system.web section is set to

false.

XML

<compilation debug=”false”>

Deactivate WCF tracing if you have previously activated it.

9. If your deployed app will use a database instance different from the one you used during development,

change the edmKey in the web.config to point to the new database. Also make sure that the security model

defined in that edmKey is the appropriate one. For example, you may have been using integrated security

during development, which might provide, through the deployed application, excessively liberal access to

the database server.151

You would want to replace this with database-based security (e.g., SQL Server

Logins).

10. Rebuild the solution to create fresh assemblies and a fresh .XAP file.

11. Copy any .aspx files, the web.config, Global.asax, EntityService.svc, EntityServer.svc, and Silverlight.js

files to the application folder.

12. Copy all assemblies from the bin folder of your development solution to the bin folder. These should

include the following assemblies, which are required by DevForce:

IdeaBlade.Core

IdeaBlade.EntityModel

IdeaBlade.EntityModel.Edm

IdeaBlade.EntityModel.Server

IdeaBlade.Linq

IdeaBlade.Validation

They should also include the assemblies from your own projects (e.g., the assembly produced from your

Silverlight project).

Note that if you subsequently modify the .aspx or Global.asax files, you must force IIS to recompile your

web application. You do this either of the following ways:

a. Rename a DLL in the bin folder, then change it‟s name back to its original name; or

b. Modify the web.config file (e.g., add a dummy comment) and save it.

151 The deployed application will run in an IIS application pool associated with a particular Windows user. Thus, any user of

your application will have the access privileges of that Windows user. You may not want them all to have that level of

access.

Page 519: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

519 | P a g e

13. Copy the XAP file from your solution‟s ClientBin folder to the ClientBin folder of the application.

14. You are now ready to test your application. Open a browser on a client machine and enter the URL for your

application. This will be:

the domain name for your web server, followed by

a forward slash and the alias of the virtual directory you created, followed by

the startup file for your application.

For example,

http://www.ideablade.com/MyApplication/default.aspx

The name of the startup file can be omitted if it has the name of the default startup file for the virtual

directory, e.g.,

http://www.ideablade.com/MyApplication

Deploying to IIS Version 7

1. Ensure that the XAP mime type is registered in IIS. The server must recognize the .XAP extension in order

to serve a Silverlight application. See http://learn.iis.net/page.aspx/262/silverlight/ for more information on

registering the mime type for different IIS versions.

2. The DevForce BOS runs as a WCF service, so you must also ensure that WCF is registered with IIS. See

the topic, “Ensure That IIS and WCF Are Correctly Installed and Registered” in the document at this URL:

http://msdn.microsoft.com/en-us/library/aa751792.aspx. Note that you do not need to create service files or

configure endpoints – these are provided for you in the .svc files and in the DevForce-generated web.config

(or the samples provided).

3. On the web server, create a physical directory for your application under the web site root folder (typically

c:\ inetpub\wwwroot). (The name used for the application in its URL can be different from this folder

name.)

4. Create bin, ClientBin, and log folders under the above directory. Change the NTFS permissions on the log

folder to give the Users group Modify permission. (This will allow the debug log to be written into this

folder.)

5. Create an application (or choose an existing one) in IIS. You can do this using the Internet Information

Services Manager.

To use the physical folder name as the alias... To use an alias that is different from the name of the

physical folder...

Page 520: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

520 | P a g e

The web site will normally host both the Silverlight application (the .html, .aspx, and .xap files) and the

DevForce BOS. (Hosting the BOS at a different site as the Silverlight application requires additional setup

steps not covered here, including the provision of cross-domain policy.)

6. While still in the Internet Information Services Manager, browse to and select the application‟s log folder.

Click the Authentication icon under the IIS section, and disable Anonymous Authentication. This will

prevent non-authorized persons from examining the application’s debug log.

7. Make any necessary changes to the app.config file in your Silverlight project. For example, we might

change the objectServer section from this (appropriate for development on a single machine:

XML

<objectServer isDistributed="true"

Page 521: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

521 | P a g e

remoteBaseURL="http://localhost "

serverPort="9009"

serviceName=" EntityService.svc"

/>

to this:

XML

<objectServer isDistributed="true"

remoteBaseURL="http://www.ideablade.com"

serverPort="80"

serviceName="SilverlightConsole/EntityService.svc"

/>

8. If deploying to production, turn off any diagnostics or debugging settings in your web.config. Specifically,

Make sure the debug attribute of the <compilation> element in the system.web section is set to

false.

XML

<compilation debug=”false”>

Deactivate WCF tracing if you have previously activated it.

9. If your deployed app will use a database instance different from the one you used during development,

change the edmKey in the web.config to point to the new database. Also make sure that the security model

defined in that edmKey is the appropriate one. For example, you may have been using integrated security

during development, which might provide, through the deployed application, excessively liberal access to

the database server.152

You would want to replace this with database-based security (e.g., SQL Server

Logins).

10. Rebuild the solution to create fresh assemblies and a fresh .XAP file.

11. Copy any .aspx files, the web.config, Global.asax, EntityService.svc, EntityServer.svc, and Silverlight.js

files to the application folder.

12. Copy all assemblies from the bin folder of your development solution to the bin folder. These should

include the following assemblies, which are required by DevForce:

IdeaBlade.Core

IdeaBlade.EntityModel

IdeaBlade.EntityModel.Edm

IdeaBlade.EntityModel.Server

IdeaBlade.Linq

IdeaBlade.Validation

152 The deployed application will run in an IIS application pool associated with a particular Windows user. Thus, any user of

your application will have the access privileges of that Windows user. You may not want them all to have that level of

access.

Page 522: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

522 | P a g e

They should also include the assemblies from your own projects (e.g., the assembly produced from your

Silverlight project).

Note that if you subsequently modify the .aspx or Global.asax files, you must force IIS to recompile your

web application. You do this either of the following ways:

c. Rename a DLL in the bin folder, then change it‟s name back to its original name; or

d. Modify the web.config file (e.g., add a dummy comment) and save it.

13. Copy the XAP file from your solution‟s ClientBin folder to the ClientBin folder of the application.

14. You are now ready to test your application. Open a browser on a client machine and enter the URL for your

application. This will be:

the domain name for your web server, followed by

a forward slash and the alias of the virtual directory you created, followed by

the startup file for your application.

For example,

http://www.ideablade.com/MyApplication/default.aspx

The name of the startup file can be omitted if it has the name of the default startup file for the virtual

directory, e.g.,

http://www.ideablade.com/MyApplication

Troubleshooting

Problem:

Your application was running initially and then crashes after a few minutes with an exception message such

as: “Object reference not set to an instance of an object.. ---> System.NullReferenceException: Object

reference not set to an instance of an object”.

Solution:

You may have encountered a problem that occurs when the IIS application pool has recycled. One of the

best ways to insure this does not happen is to create a new application pool that does not recycle on a time-

limited basis, and then assign your application to that pool.

Problem:

Your application had been running and then crashes after you make a change to one or more of the files in

the application directory. The exception includes this message: “Could not load file or assembly

'App_Web_...” .

Solution:

You may have encountered a problem that occurs when files in the application folder no longer match the

compiled version located in the “Temporary ASP.NET Files” folder. You can force a rebuild of your

application by deleting the “bin” folder and then replacing it with a copy, or by running the

“aspnet_compiler.exe” command with the “-c” switch. You can find the command by first browsing to the

Page 523: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

523 | P a g e

folder “%SystemRoot%\Microsoft.NET\Framework\” and then opening the v2.0.xxxxx subfolder (the

numbers after v2.0 can vary) .

Here is an example using the virtual directory name of the application (here, MyApp):

aspnet_compiler –v /MyApp -c

Resources

Check the Windows Event Viewer for error messages from "DevForce Object Server" and "ASP.NET".

Examine the DevForce debug log, named DebugLog.xml and located by default in the log folder.

Deploying a DevForce WinClient Application

Overview

We enjoy an amazing productivity advantage building DevForce “smart-client”153

applications: we don‟t have to

think about deployment issues until remarkably late in the game.

We may complete initial development without deploying anything. All we need is our development PC with its

private copies of .NET, the DevForce framework, a relational database server, and a test version of the application

data source. We compile and execute on our PC. We may use our Internet connection to reach a web service. That is

about the extent of it.

We don‟t have to program in any special way to ready the application for an n-tier deployment. We should not have

to change a line of code to deploy to a load-balanced, multiple-server production environment, serving global clients

from behind a firewall.

Eventually we face deployment. A smart-client deployment distributes functionality between host and client

environments. In DevForce we enable this distribution by copying one group of files to the host, another group to

the client, and by setting values in a configuration file.

Deployment Configurations

DevForce WinClient applications can be deployed in the following basic configurations:

Name Description Specific Deployment

Task

Comments

Single-Tier Single-Tier, rich-client

application with local data

store.

Local data store

deployment

DevForce Client

deployment

May be shrink-wrapped

consumer software.

Client-Server 2-Tier application with a rich

client connecting to back-end

data server.

Data Server deployment

DevForce Client

deployment

Requires a functioning database

connection for each client;

security, reliability, and

scalability limitations. Easy to

deploy.

153 This term has been used in the industry to denote different things: we refer to an n-tier application deployed across the

internet with a WinClient front end.

Page 524: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

524 | P a g e

Name Description Specific Deployment

Task

Comments

Smart-Client 3-Tier, Smart-Client

application. Rich client

connects to a middle-tier

DevForce Business Object

Server, which in turn connects

to a Data Server.

Data Server deployment

DevForce Business

Object Server deployment

DevForce Client

deployment

Provides the best security,

performance, and reliability.

Can support disconnected

operation. Complex deployment.

What are the Parts of Your Business Model, and Where Are the Parts Deployed?154

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.

154 This section is revisited from the “Hello DevForce” chapter, and expanded compared to the treatment there.

Page 525: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

525 | P a g e

The .EDMX and .IBEDMX files, and their corresponding .NET code files, are required only at design time.

At run time, your application requires the compiled assemblies associated with the projects in which reside the

Entity Data Model(s) and the Domain Model.

The Entity Data Model { assembly is / assemblies are } deployed only on the server, and { is / are } used by the

Entity Framework at run time.

The Domain Model assembly is deployed on both client and server.

One additional set of items is required on the server at runtime: the Entity Data Model‟s “artifact” files. These files,

which have extensions of .SSDL, .CSDL, and .MSL, are extracted from the EDMX model, and represent the

storage, conceptual, and mapping schemas defined in that model, respectively.

At build time, these files (by default) are embedded as resources in the assembly created for the Entity Data Model

project. Alternatively, they can be written as stand-alone disk files into the output directory for that project. The

destination is controlled by a property, MetadataArtifactProcessing, of the Entity Data Model.

At run time, the artifact files for each Entity Data Model must be available to the Entity Framework either in the

EDM‟s assembly, or in locations specified in the App.Config file used by the DevForce EntityServer.

Deployment Phases

Initial Deployment – Most of this chapter concerns the initial deployment of the application in both its server-side

and client-side aspects. The hard work is done once we have the first application version successfully installed on a

server and a client. We will describe different deployment scenarios based on application/deployment types.

Deployment Test – This is critical to a successful application deployment. It may be necessary to deploy a complex

application in multiple stages and test each deployment stage thoroughly.

Application Update – Updating the application with successive versions, a topic covered later in this chapter, rests

on the foundation of the initial deployment and testing of such deployment.

Page 526: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

526 | P a g e

Initial WinClient Deployment In a Nutshell

DevForce initial deployment, simple or complex, boils down to the following activities:

Update DevForce configuration settings in the app.config file – this is in XML format and is required

for both client and/or server-side DevForce components. See the section DevForce And the App.Config

File for details.

Create server and/or client sets of assemblies and executables. See the section Create Client and Server

File Sets for more information. You will always need to create at least one set (client or server-side) of the

deployable files.

Configure the host environment and deploy the server component. This can be very complicated with

many deployment combinations if you are deploying a fully distributed n-tier enterprise/web application

that employs database server, application server, and/or web server. See the section Business Object Server

Deployment for each server component deployment.

Configure the client environment and deploy the client component to match the intended client

deployment model. Installing the client set on the client machine can be as simple as clicking on a web-

page link. This step is unnecessary if you are deploying ASP.NET web application. See the section Client

Deployment.

Test the deployment. This is important particularly for more complicated application deployment. See

the section Deployment Test for more information.

Not all steps are required for all deployment configurations. Study the following sections to identify your

deployment configuration type and see the correct set of tasks to perform.

Deploying a Single-Tier WinClient Application

1. Update the app.config file settings, as detailed in DevForce And the App.Config File. You need only a client-

side configuration file with database server etc.

2. Create the client-only file set. See the section Create Client and Server File Sets for more information.

3. Configure and deploy a local data store for the application. Typically, a file base data store is deployed by

application.

4. Configure the client environment and deploy client files. See the section Client Deployment.

5. Test Deployment. See the section Deployment Test.

6. Refer to the section Troubleshooting as needed.

Deploying Two-Tier (Client-Server) WinClient Applications

1. Update the app.config file settings, as detailed in DevForce And the App.Config File. You need only a client-

side configuration file with database server etc.

2. Create the client-only file set. See the section Create Client and Server File Sets for more information.

3. Configure and deploy a database server for the application. See the sedction Data Server Deployment.

4. Configure the client environment and deploy client files. See the section Client Deployment.

5. Test Deployment. See the section Deployment Test.

6. Refer to the section Troubleshooting as needed.

Page 527: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

527 | P a g e

Deploying N-Tier (Smart-Client) Applications

1. Update the app.config file settings, as detailed in DevForce And the App.Config File. You need both client-

and server-side configuration files, set for remoting with database server settings, etc.

2. Create both client and server-side file sets. See the section Create Client and Server File Sets for more

information.

3. Configure and deploy database server for application. See the sedction Data Server Deployment.

4. Configure and deploy application server. See Business Object Server (BOS) Deployment section.

5. Configure the client environment and deploy client files. See the section Client Deployment.

6. Test Deployment. See the section Deployment Test.

7. Refer to the section Troubleshooting as needed.

A Bit of Advice Regarding N-Tier Deployments

Experience has taught us that the deep weeds are in the host set-up. Leaping directly from the single-user

development PC to the corporate network has been tried … with predictably unpredictable results.

We strongly encourage you to do as we do: take incremental steps on a spiral outward from deployment on

a single machine to deployment on the fully-networked environment. Test each step before advancing to

the next:

1. One-box deployment to your own PC

2. Two-box deployment of the server file set to another PC connected by an intranet.

3. Three-box deployment: test external client reaching the PC server from outside the firewall

4. One-box deployment using a web server, again first on your own PC

5. Two-box deployment using a web server using another PC on the intranet

6. Deploy to the company web server and test client deployment from outside the firewall

7. Deploy to production-ready application server; connect to the data source server(s)

8. Add HTTPS to the solo PC deployment

9. Add HTTPS to the intranet web server deployment

10. Add HTTPS to the extranet web server deployment

The major hurdle tends to be the transition to the web server. Web servers can be challenging to configure

properly. We cover the essentials for an IIS web server deployment in this document.

Building Blocks

In this section you will find detail on steps referenced in the deployment recipes provided above.

Page 528: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

528 | P a g e

Create Client and Server File Sets

If you are deploying a one- or two-tier application, you should only need to create one set of the client-side files.

You may simply follow the Client-Side File Set in the left column in the table below.

Deploying to more than two tiers, however, requires separation of the application files into two sets, a client and a

server set.

While we rarely pay much attention to tier separation when writing our code, there a few places where we have to

consider the actual runtime environment. Some code will run on the server, some on the client, and some on both.

The decision process is not difficult. The file sets usually align as follows:

Component Client-Side File Set (all deployment types) Server-Side File Set (deployment to

more than two tiers)

Application

executable (EXE)

and related UI

assemblies

MyApp.exe (example)

IdeaBlade.UI.dll

IdeaBlade.UI.WinForms.dll

Optional 3rd party UI control assemblies

- not needed -

DevForce UI

control assemblies

(one or more) 155

IdeaBlade.UI.WinForms.DotNetControls

IdeaBlade.UI.WinForms.DevExpressControls

IdeaBlade.UI.WinForms.Infragistics.Controls

- not needed -

DevForce Business

Object Server

(BOS)

IdeaBlade.EntityModel.Server.dll 156 IdeaBlade.EntityModel.Server.dll157

ServerConsole.exe (Console App)

ServerService.exe (Win Service)

Web.config and Global.asax (IIS)

Application

business object

assemblies

AppHelper.dll 158

<DomainModel name>.dll(example)

<EntityDataModel name>.dll159

AppHelper.dll

<DomainModel name>.dll

<EntityDataModel name>.dll

App.config160

app.config (Client version) app.config (Server version)

155 The DevForce UI control assemblies you need depend upon which 3rd party UI control suites you‟re using.

156 We need IdeaBlade.EntityModel.Server.dll on the client only if deploying in single-tier or 2-tier (“client / server”) mode.

Exclude this library from the client if deploying as an n-tier application with the Business Object Server (BOS).

157 We always need IdeaBlade.EntityModel.Server.dll on the Server. We must add the ServerConsole.exe if deploying the BOS

as a console application. We add the ServerService.exe instead if deploying the BOS as a Windows Service. We must include

Global.asax and Web.config files if deploying the BOS as IIS service.

158 Include AppHelper.dll in this category, if you have one. The AppHelper project is no longer generated by the DevForce

Object Mapper, but some pre-existing applications still use it to house the app.config file.

159 Needed on the client only if deploying in single-tier or 2-tier (“client / server”) mode. Exclude this library from the client if

deploying as an n-tier application with the Business Object Server (BOS).

160 Include only if deploying a loose app.config file. Do not include this if the app.config is a resource embedded either in the

executable or in AppHelper.dll.

Page 529: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

529 | P a g e

Component Client-Side File Set (all deployment types) Server-Side File Set (deployment to

more than two tiers)

DevForce

persistence library

assemblies

IdeaBlade.EntityModel.dll

IdeaBlade.Core.dll

IdeaBlade.Validation.dll

IdeaBlade.Linq.dll

IdeaBlade.EntityModel.dll

IdeaBlade.Core.dll

IdeaBlade.Validation.dll

DevForce

TraceViewer

(optional)

WinTraceViewer.exe WinTraceViewer.exe

Server Deployment

This section addresses deployment of the server-side components needed for a DevForce application. We address

configuration settings for the host environment; DevForce-specific requirements for the data server; and deployment

options for the DevForce Business Object Server.

Note that it is possible to develop a Microsoft Windows Installer (MSI) package to deploy all server components

described here.

Business Object Server (BOS) Deployment

The DevForce equivalent of an “application server” consists of its “Business Object Server” executable and a subset

of the application assemblies.

The application server must meet the following requirements:

1. Run a Microsoft operating system: Windows XP Professional, Windows Vista, Windows 2000 Server,

Windows 2003 Server, Windows 7, Windows 2008 Server, or some successor MS O/S.

2. Run the Microsoft .NET Framework version 3.5.

3. Have access to the data source server(s).

4. Be visible to client machines on the Internet, an intranet or some network supporting HTTP or TCP.

5. Designate an application server directory that holds the “Business Object Server” assembly, the

DevForce framework assemblies, and other auxiliary assemblies. A directory such as

“C:\MyDevForceApplication” will do.

BOS Deployment Scenarios

Essentially, BOS can be deployed in three basic scenarios:

1. Console Application

2. Windows Service

3. Microsoft IIS Service

Here are some of the differences in characteristics and usages:

Page 530: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

530 | P a g e

Console Application Windows Service Microsoft IIS Service

Benefits Easiest to deploy – easy

to update

Relatively easy to deploy –

restarts BOS when server

reboots

Most difficult to deploy –

uses port 80 – allows SSL

setup

Usage Best use is during

development stage

Great production

deployment - intranet Great production

deployment – internet &

intranet

Main entry file ServerConsole.exe ServerService.exe Web.config

Business Object

assemblies <DomainModel name>.dll

AppHelper.dll

IdeaBlade

assemblies

IdeaBlade.EntityModel.Server.dll

IdeaBlade.EntityModel.dll

IdeaBlade.Core.dll

IdeaBlade.Validation.dll

IdeaBlade.Linq.dll

IdeaBlade.EntityModel.Edm.dll

Optional files app.config app.config web.config

global.asax

testasa.aspx

In order to simplify the documentation of the BOS deployment task, we‟ll employ these conventions in illustrating

each of the deployment scenarios:

We will use an embedded app.config resource inside the domain model assembly rather than a loose

app.config file.

We‟ll say that the application name is MyApp.

We‟ll say that the Business Object assembly name is DomainModel.dll.

We‟ll say that the target server name is SERVER2003R2. This server will be assumed also to host the IIS.

We‟ll say that the data server name is SQL2005, and that the SQL user sa has access to its databases.

Deploying As a Console Application

Deploying the BOS as a Console Application is the easiest way in a three-tier environment. It is particularly useful

when your application is still in the development stage. You can easily update your deployed server files with

minimal changes. Most of the time, you will only need to change your app.config file. The following steps outline

how to deploy the BOS as a Console Application.

1. Prepare the server-side file set. In particular, you need to change the IdeaBlade section of the app.config

file inside the domain model assembly: set isDistributed to true and specify the target remoteBaseURL.

The default server name and port values can use the default settings. Also, you should not use Integrated

Security in the connection string of your edmKeys.

XML

<edmKey

connection="metadata=.\NorthwindIB.csdl|.\NorthwindIB.ssdl|.\NorthwindIB.msl;pr

Page 531: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

531 | P a g e

ovider=System.Data.SqlClient;provider connection string=&quot;Data

Source=Sql2005;Initial Catalog=NorthwindIB;User Id=sa;Password=password;

MultipleActiveResultSets=True&quot;"

containerName="NorthwindIB.ServerModel.NorthwindIBContext"

logTraceString="false"

name="NorthwindIB" tag="">

<probeAssemblyNames>

<probeAssemblyName name="NorthwindIB.DomainModel.CS" />

<probeAssemblyName name="NorthwindIB.ServerModel" />

</probeAssemblyNames>

</edmKey>

<objectServer isDistributed="true" remoteBaseURL="http://SERVER2003R2"

serviceName="EntityService" serverPort="9009" proxyName="" proxyPort="0"

sessionEncryptionKey="" />

2. Recompile all modules including the client application.

3. Create the server-side file set:

ServerConsole.exe

<DomainModel name>.dll

AppHelper.dll (if used)

<EntityDataModel name>.dll

IdeaBlade.EntityModel.Server.dll

IdeaBlade.EntityModel. v4.dll

IdeaBlade.Linq. v4.dll

IdeaBlade.EntityModel. Edm.dll

IdeaBlade.Core.dll

IdeaBlade.Validation.dll

app.config (optional)

4. Create a directory on the server.

5. Copy the server-side file set above to that directory.

6. Optionally, configure the server-side copy of app.config file.

7. Run ServerConsole.exe in the target server directory. It should display something similar to this:

The BOS is now ready to communicate to its remote client applications.

Deploying As a Windows Service

To deploy the BOS as a Windows Service, perform the steps outlined in the previous section, “Deploying As a

Console Application”, with the following changes:

1. Include ServerService.exe instead of ServerConsole.exe in your server-side file set.

2. Instead of running ServerConsole.exe in the target server directory, you must first install ServerService.exe

as a Windows service. Do this as follows:

a. Launch the Visual Studio command prompt from the Windows Start menu at Start / Microsoft

Visual Studio 2008 / Visual Studio Tools / Visual Studio 2008 Command Prompt

Page 532: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

532 | P a g e

b. Change to the directory to which you have copied the server-side file set.

c. At the Command Prompt, run the InstallUtil tool as follows:

d. After running InstallUtil, the IdeaBlade DevForce Entity Service will be installed as a Windows

service. You can now start, stop, and configure this service using the Services plug-in in the

Microsoft Management Console (available at Control Panel / Administrative Tools / Services).

When the service is running, the BOS is available.

Deploying on Microsoft IIS

DevForce applications can be deployed on Microsoft IIS. Some of the reasons you may want to deploy under IIS

are:

You already have thin-client applications served by IIS.

You typically want the application to use port 80 (which can, however, be changed) on a server hosting IIS.

You want to use Secure Socket Layer (SSL) to encrypt data flowing between the server and client.

Your application sports both a smart-client and a browser-based UI.

Typically, deploying the BOS as a Microsoft IIS service is more difficult than deploying it as a Console Application

or Windows Service. This is because you have to modify many settings in different files.

We recommend that you deploy your DevForce application first as a Console Application to work out all

the communication issues, before attempting to install it as an IIS service.

If you are familiar with creating web sites in IIS, the easiest deployment method may be manually to create a new

virtual directory for the BOS and then copy the needed files.

If unfamiliar with IIS, you can create a web setup project to aid the IIS deployment. In brief, the steps are:

1. Prepare the server-side file set. Besides changing the app.config file, you may also need to update the

Web.config file. There are two optional files – Global.asax and Test.aspx – that can be used as-is, without

any change. All three files are included as sample files in the DevForce tutorial “IIS Deployment” installed

at LearningResources\110_Deployment\Samples\300COR_DeploymentWithIIS.

Including the Global.asax is optional – it only verifies that an IdeaBlade configuration was found and

supports remote viewing of the server debug log.

Including the TestAsa.aspx is optional – it only helps developer to determine whether the web.config

contains errors.

The Web.config file tells IIS how to start the BOS. Typically, you can use this file as-is.

EntityService.svc and EntityServer.svc define the WCF services.

In IIS, the <ObjectServer> section of your IdeaBlade configuration is ignored, and can be removed.

2. Create a virtual directory to host the BOS.

Page 533: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

533 | P a g e

3. Create a bin sub-directory, and place in it all assemblies (and metadata artifact files) identified above as

necessary.

You will need *.svc (service file) for all services.

You can copy the EntityService.svc file w/o changes.

You will need an EntityServer*.svc file for each data source extension used. See the

EntityServer.svc and EntityServer_Dev.svc samples for guidance.

4. Copy the global.asax file.

By default this file contains startup logic to verify that an IdeaBlade configuration was found and to enable

use of the TraceViewer. You can change this as needed. Any errors during startup will be written to the

Event Viewer, and possibly the debug log (DebugLog.xml) if one could be created. (Not being able to

create a log file is a very common error encountered as you work through initial deployment issues).

5. Decide where your IdeaBlade configuration information will be located.

In IIS, a web.config file is required, and the Object Server information must be defined in the

System.ServiceModel section. You still need the <ideaBlade.configuration> section, though, to configure

the other IdeaBlade features.

You have two options for placement of the IdeaBlade configuration section:

Add the <ideaBlade.configuration> section to the web.config file. (See web.config.sample1 for more

details.)

Embed the app.config in an assembly such as the domain model assembly. (See web.config.sample2

for more details.)

6. Decide where your log file will be written.

If a full path to the logFile is not specified, the location defaults to the application directory holding the

web.config file. Placing the log file here is not a good idea, since all users will be able to browse to the

debuglog.xml using their web browser and will therefore see all your secrets! You should therefore either

specify a secure directory for the log file, or turn off xml file browsing.

We recommend placing the debug log file in a "log" subdirectory from which all IIS permissions have been

removed.

To place the debuglog in a non-default directory, you can specify a full path name, or a relative path name

(such as logFile="log\DebugLog.xml"). Note that you cannot use the ~ character or virtual paths here.

Don't put the debug log in the "bin" subdirectory, because writes to the it will cause IIS to recompile all

assemblies there.

See the 300COR_DeploymentWithIIS code sample in the Deployment topic folder of the Learning Resources for

more information.

Hosting the BOS on SSL

Secure Socket Layer (SSL) is the industry standard approach to encrypting data transmitted over the Internet and

thus protecting the confidentiality of data in transit.

Page 534: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

534 | P a g e

There is nothing unusual about engaging SSL for a DevForce application and its BOS. The most troublesome

procedure would probably be adding a valid security certificate into the target server. You must ask your network

administrator to ensure it is set up correctly.

Let‟s assume we deployed the BOS in IIS as described in the previous section, and that we have put in place a loose

app.config file for the BOS using the steps from that section. We would like to deploy the DevForce BOS using

SSL at port 443.

We would need to do the following:

1. Ensure that the server‟s Firewall (if any) does not block the SSL port – 443.

2. Ensure that a valid security certificate is installed at the target server.

a) Locate and start the Internet Information Services (IIS) Manager at the server.

b) Expand SERVER2003R2(local computer) > Expand Web Sites > Expand Default Web Site.

c) Select and right-click Default Web Site to display its context menu.

d) Select Properties to display the Default Web Site Properties dialog.

e) Select Directory Security tab.

f) Click on the View Certificate button to show the Certificate dialog. If this button is

disabled, then no valid certificate was installed in this server. You must stop and ask your

network administrator to obtain a valid security certificate for the target server.

3. Update the IIS to listen on SSL port 443.

4. Again display the Default Web Site Properties dialog.

Select Web Site tab.

Set SSL port value to 443.

Click OK to save.

Now the DevForce client application will communicate to the BOS via SSL at port 443 using the HTTPS protocol.

Using the TraceViewer Under IIS

You likely will want to enable Trace Viewing when deploying your application on IIS.

Call the TracePublisher.LocalInstance.MakeRemotable() method in the Application_Start method of

the Global.asax.cs file of your IIS application. The TracePublisher class is in IdeaBlade.Core.

// C# protected void Application_Start(Object sender, EventArgs e) { IdeaBladeConfig.ConfigFileAssembly = Assembly.GetExecutingAssembly(); IdeaBlade.Core.TracePublisher.LocalInstance.MakeRemotable(); }

' VB Protected Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs e) IdeaBladeConfig.ConfigFileAssembly = Assembly.GetExecutingAssembly() IdeaBlade.Core.TracePublisher.LocalInstance.MakeRemotable() End Sub

Client Deployment

Most DevForce WinClient customers prefer to use ClickOnce deployment for their client applications.

Parts of this section describe client application deployment with a Microsoft Installer package. These techniques

may still be useful in circumstances where ClickOnce is inappropriate.

Page 535: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

535 | P a g e

Configure the Client Environment

No matter how we deploy to the client, the client must:

run a Windows O/S that supports the Microsoft .NET Framework version 3.5;

have installed Microsoft .NET Framework version 3.5; and

be able to access the application server on the Internet, intranet, or network

Security Permissions

Microsoft .NET offers a wide range of client-side security options covering fine grained control of operations and of

access to local resources such as the file system. There are ways to trust certain assemblies more than others. It is a

rich topic, beyond the scope of this document.

At the extremes are (a) wide-open access such as we have when we are logged-in to Windows with administrator

privileges and (z) the most restrictive security setting in the IE browser.

“Permanent” installation in a client PC directory may require more permission than is routinely available on your

users‟ PCs. Most applications don‟t need unfettered access and it is appropriate to restrict client boundaries to just

what the application requires. There are a variety of ways to set local security options automatically prior to

installing the application. You may wish to consult a DevForce professional to discuss the pros and cons and

discover what is right for you.

We can run a smart-client application inside an IE browser configured with the standard browser security settings.

Application files load into the temporary browser cache instead of a PC directory. However, many smart-client

features are not available in this deployment model. For example, the browser only supports 100% managed code;

whereas many 3rd

party UI controls are built with unmanaged code, and won‟t run.

MSI Client Deployment

As on the server, “installation” means “copy the files to a specific location”. To make it easy for the user, we usually

provide a Microsoft Windows Installer (MSI) file to perform setup.

The user first has to get this setup file. She might download it from a web page or copy it from a CD. Then she

executes it, answers a few questions, and the setup utility drops the files into the (user) designated directory.

There are no hidden files, nothing added to the Window directory, and – most importantly – no Windows Registry

entries! We might get fancy and offer to add a shortcut to the Windows Start Menu and desktop.

The following steps show you how to create a simple client Setup Project from Visual Studio:

1. Prepare the client-side file set.

2. Create a new Setup Project under your Solution Explorer to include the client-side file set.

a) Right-click your solution entry inside the Solution Explorer, select Add, and then select

New Project.

b) Inside the Add New Project dialog, select Other Project Types > Setup and Deployment

project type.

c) Select Setup Project template on the right pane.

d) Enter the target project name (e.g. MyWinApp) and click OK to create the new project.

3. Add all client-related files and assemblies into the Application Folder of the Setup Project.

a) Select the File System Editor view under the Solution Explorer tool bar.

b) Right-click on the Application Folder, select Add, and select Project Output to display the

Add Project Output Group dialog.

4. Select the Primary output of your application project (e.g. MyApp) and click OK.

Page 536: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

536 | P a g e

Note: this step also brings in other assemblies including DomainModel.dll161

. Optionally, you may

include the app.config file as needed.

5. Rebuild all projects including the new Setup Project – MyWinApp. It should produce two files in its

output folder – Setup.exe and the msi file (e.g., MyWinApp.msi).

6. Copy them into the target client machine or a shared server directory.

7. Run Setup.exe to install the client DevForce application. The Setup Wizard will come up – just follow

the on-screen instructions of the wizard to complete.

De-installation is therefore safe and obvious: the client simply deletes the directory and some shortcuts.

InstallShield and MSI have no trouble with these chores.

When we deploy the client application within the browser environment, the browser does the copying for us, but

into its own cache rather than a “permanent” installation directory. There is no explicit de-installation; clearing the

browser cache does that trick.

ClickOnce Client Deployment

This is relatively straightforward if you build your client application using the ClickOnce publish option. Simply

publish your application‟s ClickOnce file set into the target host server for users to download and install.

Here are the steps to publish MyApp from Visual Studio to a shared network directory:

1. Navigate to and select your client application‟s executable project in Solution Explorer. E.g. MyApp.

2. Right-click and select Properties in the context menu to display MyApp properties in the main pane.

3. Select Publish tab.

4. Fill in all necessary publish information and options. For most settings, just use the default values.

5. Set the Publishing Location (E.g. \\Server2003r2\Public\ClickOnce\MyApp\)

6. Set the Publish Version.

7. Click the Application Files button to change all IdeaBlade assemblies to be included for the client

deployment. Inside the Application Files dialog, select each IdeaBlade assembly, and then change the

Publish Status from Prerequisite (Auto) to Include.

8. Click OK to save and return to the Publish tab.

9. You can use the Publish Wizard to walk through the rest of the settings, or simply click Publish Now.

10. Upon completion of the publishing step above, Visual Studio will automatically open up the publishing

directory or site.

161 …and AppHelper.dll, should you be using that

Page 537: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

537 | P a g e

11. You can then click the Install button to install MyApp from the published site to your local client

machine.

Application Update for ClickOnce

One of the great features of ClickOnce is that it can auto-update your deployed client application. By default, a

ClickOnce-published application will check for newer updates before it starts.

Let‟s assume we made some changes to MyApp. Here are the typical next version deployment steps:

Recompile the MyApp project or its solution.

1. Expand and select the MyApp project in Solution Explorer.

2. Right-click and select Properties in the context menu to display the MyApp properties in the main

pane.

3. Select the Publish tab. Note that the Publish Version had already been updated to 1.0.0.1 from the last

ClickOnce publish.

4. Click Publish Now to publish the new version.

5. Upon completion, Visual Studio will open up the publishing directory/site. Note that the Version

number at the site is now 1.0.0.1.

6. Do not install and simply close the install site.

7. Instead go to a client machine that had previously installed version 1.0.0.0 of the application.

8. Run MyApp 1.0.0.0.

9. A dialog will display as shown below to tell you a new version is available for MyApp.

Page 538: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

538 | P a g e

10. Simply click OK and MyApp will be updated to the new version 1.0.0.1 and running along.

Other Reading on ClickOnce

There are many options and features in ClickOnce, and you may want to learn more about them. Links are provided

below to help you to do so.

Other Useful Sources on ClickOnce

Title Location

Smart-Client Deployment with

ClickOnce(TM): Deploying Windows Forms

Applications with ClickOnce(TM). Book by

Bryan Noyes, published Dec.2006 (Microsoft

.NET Development Series)

Introducing Client Application Deployment with

“ClickOnce”, Duncan Mackenzie

http://msdn.microsoft.com/library/default.asp?url=/library/en-

us/dnwinforms/html/clickoncetrustpub.asp

“ClickOnce for the Real World, not Hello World”,

Julia Lerner

http://www.code-magazine.com/Article.aspx?quickid=0611041

“ClickOnce: Bringing Ease and Reliability to

Smart-Client Deployment”, Patrick Darragh

http://www.code-magazine.com/Article.aspx?quickid=0601041

“Administering ClickOnce Deployments”, Bryan

Noyes

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwinforms/html/clickoncetrustpub.asp

“Signing your ClickOnce Manifest”, Bryan Noyes http://msdn.microsoft.com/library/default.asp?url=/library/en-

us/dnwinforms/html/clickoncetrustpub.asp

ClickOnce Limitations

ClickOnce has a number of limitations, among them the following:

Applications are installed for a single user. You cannot accommodate every user of the workstation in a

single install.

Applications are installed in the system-managed folder specific to the user. You cannot change or

influence the choice of the installation folder.

Page 539: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

539 | P a g e

You can provide for exactly one Start Menu short cut for the application. It must appear in the form

[Publisher Name] ► [Product name]. You can‟t change this. You can‟t add supplemental short-cuts

such as one that points to an uninstall, a help file, or a support page on a web site. You can‟t add short-

cuts to any other folder such as StartUp or Favorites.

You can‟t change or enhance the setup wizard with additional dialogs and options.

You can‟t change the how Click Once generates the installation web page. You can modify its HTML

after it has been generated.

You can‟t install DLLs into the Global Assembly Cache (GAC).

The installation cannot perform custom actions such as creating a database, registering file-types for the

Windows Explorer window, or make any changes to the Registry.

You can work around some of these limitations by, for example, having your application perform them on start-up.

However, you‟ll want to switch to an MSI or 3rd

party installer if your requirements get even a little complex. You

must then abandon the Click Once auto-update feature.

ClickOnce Deployment with a CAB application

There are some specific issues related to the use of ClickOnce with an application that incorporates the Composite

UI Application Block (CAB) from the Microsoft Patterns and Practices group. If you need to deploy such an

application using ClickOnce, please contact support via

http://www.ideablade.com/CustomerSupportRequestForm.aspx

which can supply you with some relevant third-party documentation.

Deployment Test

We found the following thoughts sufficiently important to merit a small sidebar nearer the front of this chapter, but

they are worth repeating here:

Experience has taught us that the deep weeds are in the host set-up. Leaping directly from the single-user

development PC to the corporate network has been tried … with predictably unpredictable results.

We strongly encourage you to do as we do: take incremental steps on a spiral outward from deployment on a single

machine to deployment on the fully-networked environment. Test each step before advancing to the next:

One-box deployment to your own PC

1. Two-box deployment of the server file set to another PC connected by an intranet.

2. Three-box deployment: test external client reaching the PC server from outside the firewall

3. One-box deployment using a web server, again first on your own PC

4. Two-box deployment using a web server using another PC on the intranet

5. Deploy to the company web server and test client deployment from outside the firewall

6. Deploy to production-ready application server; connect to the data source server(s)

7. Add HTTPS to the solo PC deployment

8. Add HTTPS to the intranet web server deployment

9. Add HTTPS to the extranet web server deployment

The major hurdle tends to be the transition to the web server. Web servers can be challenging to configure properly.

We cover the essentials for an IIS web server deployment in this document.

Page 540: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

540 | P a g e

N-Tier Configuration Starter

We developers spend most of our time eeking out our programs in the privacy of our own machines. It is better to

test the results on our local machines before sharing our bugs with our colleagues. Accordingly, when testing an n-

tier application it is wise to deploy locally first; and the N-Tier Configuration Starter is a terrific time-saver in this

respect.

The N-Tier Configuration Starter is designed as a training tool, or at best as a first step for real-world n-tier

deployments. It is serviceable for simple deployments on a solo PC but will be quickly superseded by other methods

for wider or more complex deployments.

After launching it from the Windows Start Menu: IdeaBlade DevForce / Tools / N-Tier Configuration Starter

and setting the Source Path to the folder that contains your application‟s front-end project, it looks something like

this:

Run the Deployed Application

No matter how deployed, whether on a single machine or in a multi-server distributed environment, the basic

sequence is always the same:

1. Launch ServerConsole.exe on the server(s).

2. Launch the client application.

Run the Server

For example, we might launch something like the following on our solo PC:

C:\zzzDeSvnIt\ComposingWinforms\UI\bin\debug\Server\ServerConsole.exe

The Business Object Server is up and running in a console window. It has found our business object assembly (or

assemblies).

The server can run also as a windows service. It‟s a different executable, which must be registered with windows,

but is the same in other respects.

Page 541: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

541 | P a g e

Before we move on, it‟s fun and instructive to start the Trace Viewer (Start / IdeaBlade DevForce / Tools

/ Trace Viewer):

OK, it‟s not fun yet. When we run our application, we‟ll see evidence of RemoteServer behavior appear here as it

happens.

Run the Client Application

In our example, we launch

C:\zzzDeSvnIt\ComposingWinforms\UI\bin\debug\Client\UI.exe

Our application starts, and we open a tab for editing, which causes the app to retrieve some data:

Page 542: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

542 | P a g e

Now the Trace Viewer is a little more revealing. It shows key events and a LINQ-like representation of the queries

submitted against the Entity Framework:

(A configuration option causes the SQL code generated by the Entity Framework for submission against the back-

end database to be logged as well.)

The Trace Viewer is convenient especially for seeing the effects of client actions on the server in real time. The

information displayed in the Trace Viewer (and more) is captured in the server‟s XML debug log located in the

server‟s directory. The log is called DebugLog.xml by default.

The application writes to XML debug logs on both client and server by default. These are essential resources for

understanding and debugging our applications.

If anything goes wrong, check the server debug logs early in your analysis. Logged information identifies the source

of the IdeaBlade configuration settings, the service configuration, license information, and activity against the

server.

Troubleshooting

This section includes the following topics:

MSDN: Specific Errors in ClickOnce Deployments

Errors When Publishing with Visual Studio

Using Mage

Other Errors

MSDN: Specific Errors in ClickOnce Deployments

This topic lists the following common errors that can occur when you deploy a ClickOnce application, and provides

steps to resolve each problem.

When you try to locate an .application file, nothing occurs, or XML renders in Internet Explorer, or you

receive a Run or Save As dialog box

This error is likely caused by content types (also known as MIME types) not being registered correctly on the

server or client.

First, make sure that the server is configured to associate the .application extension with content type

"application/x-ms-application".

Page 543: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

543 | P a g e

If the server is configured correctly, ensure that the .NET Framework 2.0 is installed on your computer. If the

.NET Framework 2.0 is installed, and you are still seeing this problem, try uninstalling and reinstalling the

.NET Framework 2.0 to re-register the content type on the client.

Error message says, "Unable to retrieve application. Files missing in deployment" or "Application

download has been interrupted, check for network errors and try again later"

This message indicates that one or more files being referenced by the ClickOnce manifests cannot be

downloaded. The easiest way to debug this error is to try to download the URL that ClickOnce says it cannot

download. Here are some possible causes:

- If the log file says "(403) Forbidden" or "(404) Not found," verify that the Web server is configured so

that it does not block download of this file. For more information, see Server and Client

Configuration Issues in ClickOnce Deployments.

- If the .config file is being blocked by the server, see the section "Download error when you try to

install a ClickOnce application that has a .config file" later in this topic.

- Determine whether this occurred because the deploymentProvider URL in the deployment manifest is

pointing to a different location than the URL used for activation.

- Ensure that all files are present on the server; the ClickOnce log should tell you which file was not

found.

- See whether there are network connectivity issues; you can receive this message if your client

computer went offline during the download.

Download error when you try to install a ClickOnce application that has a .config file

By default, a Visual Basic Windows-based application includes an App.config file. There will be a problem

when a user tries to install from a Web server that uses Windows Server 2003, because that operating system

blocks the installation of .config files for security reasons. To enable the .config file to be installed, click Use

".deploy" file extension in the Publish Options dialog box.

You also must set the content types (also known as MIME types) appropriately for .application, .manifest, and

.deploy files. For more information, see your Web server documentation.

For more information, see "Windows Server 2003: Locked-Down Content Types" in Server and Client

Configuration Issues in ClickOnce Deployments.

Error message: "Application is improperly formatted;" Log file contains "XML signature is invalid"

Ensure that you updated the manifest file and signed it again. Republish your application by using Visual Studio

or use Mage to sign the application again.

You updated your application on the server, but the client does not download the update

This problem might be solved by completing one of the following tasks:

- Examine the deploymentProvider URL in the deployment manifest. Ensure that you are updating the bits

in the same location that deploymentProvider points to.

- Verify the update interval in the deployment manifest. If this interval is set to a periodic interval, such as

one time every six hours, ClickOnce will not scan for an update until this interval has passed. You can

change the manifest to scan for an update every time that the application starts. Changing the update

interval is a convenient option during development time to verify updates are being installed, but it slows

down application activation.

- Try starting the application again on the Start menu. ClickOnce may have detected the update in the

background, but will prompt you to install the bits on the next activation.

During update you receive an error that has the following log entry: "The reference in the deployment

does not match the identity defined in the application manifest"

Page 544: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

544 | P a g e

This error may occur because you have manually edited the deployment and application manifests, and have

caused the description of the identity of an assembly in one manifest to become out of sync with the other. The

identity of an assembly consists of its name, version, culture, and public key token. Examine the identity

descriptions in your manifests, and correct any differences.

First-time activation from local disk or CD-ROM succeeds, but subsequent activation from Start Menu

does not succeed

ClickOnce uses the Deployment Provider URL to receive updates for the application. Verify that the location

that the URL is pointing to is correct.

Error: "Cannot start the application"

This error message usually indicates that there is a problem installing this application into the ClickOnce store.

Either the application has an error or the store is corrupted. The log file might tell you where the error occurred.

Things to verify:

- that the identity of the deployment manifest, identity of application manifest, and identity of the main

application EXE are all unique.

- that your file paths are not longer than 100 characters. If your application contains file paths that are

too long, you may exceed the limitations on the maximum path you can store. Try shortening the paths

and reinstall.

PrivatePath settings in application config file are not honored

To use PrivatePath (Fusion probing paths), the application must request full trust permission. Try changing the

application manifest to request full trust, and then try again.

During uninstall a message appears saying, "Failed to uninstall application"

This message usually indicates that the application has already been removed or the store is corrupted. After

you click OK, the Add/Remove Program entry will be removed.

During installation, a message appears that says that the platform dependencies are not installed

You are missing a prerequisite in the GAC (global assembly cache) that the application needs in order to run.

Errors When Publishing with Visual Studio

Publishing in Visual Studio fails

Ensure that you have the right to publish to the server that you are targeting. For example, if you are logged in

to a terminal server computer as an ordinary user, not as an administrator, you probably will not have the rights

required to publish to the local Web server.

If you are publishing with a URL, ensure that the destination computer has FrontPage Server Extensions

enabled.

Using Mage

You tried to sign with a certificate in your certificate store and a received blank message box

In the Signing dialog box, you must:

- Select Sign with a stored certificate, and

- Select a certificate from the list; the first certificate is not the default selection.

Clicking the "Don't Sign" button causes an exception

Page 545: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

545 | P a g e

This issue is a known bug. All ClickOnce manifests are required to be signed. Just select one of the signing

options, and then click OK.

Other ClickOnce Errors

The following table shows some common error messages that a client-computer user may receive when the user

installs a ClickOnce application. Each error message is listed next to a description of the most probable cause for the

error.

Error message Description

Application cannot be started. Contact the

application publisher.

Cannot start the application. Contact the

application vendor for assistance.

These are generic error messages that occur when the application cannot be started, and no

other specific reason can be found. Frequently this means that the application is somehow

corrupted, or that the ClickOnce store is corrupted.

Cannot continue. The application is

improperly formatted. Contact the

application publisher for assistance.

Application validation did not succeed.

Unable to continue.

Unable to retrieve application files. Files

corrupt in deployment.

One of the manifest files in the deployment is syntactically not valid, or contains a hash

that cannot be reconciled with the corresponding file. This error may also indicate that the

manifest embedded inside an assembly is corrupted. Re-create your deployment and

recompile your application, or find and fix the errors manually in your manifests.

Cannot retrieve application. Authentication

error.

Application installation did not succeed.

Cannot locate applications files on the

server. Contact the application publisher or

your administrator for assistance.

One or more files in the deployment cannot be downloaded because you do not have

permission to access them. This can be caused by a 403 Forbidden error being returned by

a Web server, which may occur if one of the files in your deployment ends with an

extension that makes the Web server treat it as a protected file. Also, a directory that

contains one or more of the application's files might require a username and password in

order to access.

Cannot download the application. The

application is missing required files.

Contact the application vendor or your

system administrator for assistance.

One or more of the files listed in the application manifest cannot be found on the server.

Verify that you have uploaded all the deployment's dependent files, and try again.

Application download did not succeed.

Check your network connection, or contact

your system administrator or network

service provider.

ClickOnce cannot establish a network connection to the server. Examine the server's

availability and the state of your network.

An error has occurred writing to the hard

disk. There might be insufficient space

available on the disk. Contact the

application vendor or your system

administrator for assistance.

This may indicate insufficient disk space for storing the application, but it may also

indicate a more general I/O error when you are trying to save the application files to the

drive.

Cannot start the application. There is not

enough available space on the disk.

The hard disk is full. Clear off space and try to run the application again.

Too many deployed activations are

attempting to load at once.

ClickOnce limits the number of different applications that can start at the same time. This

is largely to help protect against malicious attempts to instigate denial-of-service attacks

against the local ClickOnce service; users who try to start the same application repeatedly,

in rapid succession, will only end up with a single instance of the application.

Page 546: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

546 | P a g e

Shortcuts cannot be activated over the

network.

Shortcuts to a ClickOnce application can only be started on the local hard disk. They

cannot be started by opening a URL that points to a shortcut file on a remote server.

The application is too large to run online in

partial trust. Contact the application vendor

or your system administrator for assistance.

An application that runs in partial trust cannot be larger than half of the size of the online

application quota, which by default is 250

Errors During IIS n-Tier Deployment

This section concentrates on issues that arise while deploying to an IIS n-tier environment.

IIS is notorious for the variety of maddening deployment melt-downs that are traced to subtle configuration

mistakes. IIS messages are rarely helpful. Time to put on your deerstalker cap and Inverness cape and make like

Sherlock Holmes.

First confirm that testasa.aspx runs successfully, as follows:

Open testasa.aspx in your browser. If it opens without error it will show where the IdeaBlade configuration was

loaded from, and the location of the debug log. If the page fails to open, use the error information displayed to help

diagnose the problem.

Here are some common problems:

"Unable to obtain a valid configuration file".

This error is received when your <ideaBlade.configuration> section information cannot be found, or could

not be loaded for some reason. Ensure that IdeaBlade configuration is in either the web.config file or an

embedded app.config file. If using an embedded app.config, check your AppHelper project to ensure that

the app.config Build Action is "Embedded Resource".

"The type initializer for 'IdeaBlade.Core.TraceFns' threw an exception".

This error occurs at startup when DevForce cannot write to the log file for some reason. Use the Windows

Event Viewer to check for further information on this error. This problem is commonly due to folder/file

permissions, or the folder was not found.

"Unable to connect to http://localhost:80/SampleService/EntityService.svc. The server or internet

connection may be down."

First check the server debug log to see if service startup information was written to the log. If the log is

not present, then check the Windows Event Viewer to see if errors were logged there. The service may fail

to start due to the configuration and log file problems mentioned above, bad configuration information in

the serviceModel section, or a myriad of other reasons. :)

"The underlying provider failed on open."

Check the server debuglog for more information. In IIS, this message is typically received because the

ASP.NET account does not have appropriate privileges to the database. To address this, either grant the

account (often ASP.NET or NETWORK SERVICE) the privileges needed, or change the login account in

the connection string.

"The specified metadata path is not valid."

If you are using "loose" metadata artifact files (*.csdl, *.ssdl, *.msl) they are expected to be in a

<vroot>\bin directory when running IIS, and the SampleService\bin directory when running under IIS. If

you are using "embedded" artifact files, the server model assembly will contain these files as embedded

resources.

Page 547: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Deployment

547 | P a g e

The edmKey connection string should correctly reflect which type of metadata files you are using. Loose

artifact files will look something like this (only metadata portion of connection string shown):

XML connection="metadata=.\ServerModelNorthwindIB.csdl|.\ServerModelNorthwindIB.ssdl|

.\ServerModelNorthwindIB.msl;

while embedded files will look something like this:

XML connection="metadata=res://ServerModelNorthwindIB;

This concludes our discussion of Deployment. Should you have issues not covered here, please contact IdeaBlade

Customer Support here.

Troubleshooting

Troubleshooting

General Troubleshooting

Troubleshooting Silverlight Apps

Contacting Support Identifying your DevForce version

Upgrading Your Software

General Troubleshooting

11. You see long delays (5 seconds or more) the first time a query is executed and/or the first time a save is

performed.

You may benefit from using pre-generated views of the Entity Model. This is usually most helpful when the model

is large, or when there are many relationships defined, or when some tables contain a large number of columns and

foreign keys.

To use pre-generated views you will need to use the EdmGen.exe tool from Microsoft to generate “views” for the

model. You then include this generated code in your Entity Model project. See the following MSDN article for

complete directions: http://msdn.microsoft.com/en-us/library/bb896240.aspx.

Page 548: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Troubleshooting

548 | P a g e

Troubleshooting Silverlight Apps

12. You attempt to Connect to the BOS from the Silverlight client and receive the exception "An error occurred

while trying to make a request to URI 'http://localhost:9009/EntityService.svc'"

Connection errors can have many causes, but the first thing to check, especially in a new application using the

ASP.NET Development Server, is that the Silverlight application is actually "served" by the web application.

You can see this by looking at the address bar in the browser. If it doesn't start with "http://" then the

application is instead loading from the file system. Why is this a problem? Because, for security reasons, a

Silverlight application cannot make service requests unless served by a web server. In DevForce Silverlight this

means that the application cannot connect to, or make other requests of, the BOS; thus, data cannot be retrieved

from or saved to the back-end data source.

The problem is easily remedied by ensuring that the web application project is always the startup project in your

solution.

13. "No license found after probing all assemblies in the config file - Check for valid probeAssemblyNames in the

config file." Possibly seen when double-clicking the “Error on page” icon in Internet Explorer and viewing the

detailed error message.

The probeAssemblyNames in the app.config embedded in the Silverlight application must be fully qualified

assembly names. If not, since Silverlight is not able to load partial assembly names, no assemblies can be

"probed" and no license found. DevForce will ensure the probeAssemblyName is correct if you set the

updateFromDomainModelConfig setting in the file to either "Ask" or "Yes". This synchronization takes place

at build time.

The fully-qualified assembly name might look something like this:

XML <probeAssemblyName name="FirstSilverlightApp, Version=1.0.0.0, Culture=neutral,

PublicKeyToken=null" />

Probed assemblies are used by DevForce not only for validation of the product license, but also to determine the

location of the domain model classes and for custom interface implementations.

14. "*** License violation *** - 'Distributed BOS' not supported with the current license: StandardEF"

You must have a license for DevForce Silverlight in order to develop Silverlight applications with DevForce.

The Silverlight samples in the Learning Units were created with an SL license key and you'll be able to run the

samples as long as you don't regenerate the domain model. Once you regenerate the model with your license

key, the sample may stop working due to the license violation.

15. I get the following exception when trying to fetch: "Unable to locate type: XX.YY"

This not-so-friendly message may be caused by a type name mismatch between your .NET and Silverlight

domain model assemblies. DevForce will seamlessly transmit entities between the SL and BOS tiers, but it

does this using what is essentially a "shared" domain model. DevForce expects to see entities having the same

fully-qualified type name, for instance "DomainModel.Customer, DomainModel, Version=1.0.0.0,

Culture=neutral, PublicKeyToken=null", in both the .NET and Silverlight assemblies holding the model.

This is why DevForce attempts to keep the assembly and namespace names in sync between the two projects,

since without this type name equality, entities cannot move between tiers. This restriction will likely be

removed in later releases of DevForce Silverlight. To fix the exception, ensure that the assembly and

namespace names of the two projects containing the domain model are identical.

16. Why aren't my breakpoints working?

Page 549: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Troubleshooting

549 | P a g e

This has nothing to do with DevForce, but we run into it from time to time. Double-check the Web properties

on the web application project, and ensure that both ASP.NET and Silverlight debuggers are checked.

17. Your application was running initially and then crashes after a few minutes with an exception message such as:

“Object reference not set to an instance of an object.. ---> System.NullReferenceException: Object reference

not set to an instance of an object”.

You may have encountered a problem that occurs when the IIS application pool has recycled. One of the best

ways to insure this does not happen is to create a new application pool that does not recycle on a time limited

basis and then assign your application to that pool.

18. Your application had been running and then crashes after you make a change to one or more of the files in the

application directory. The exception includes this message: “Could not load file or assembly

'App_Web_........”.

You may have encountered a problem that occurs when files in the application folder no longer match the

compiled version located in the “Temporary ASP.NET Files” folder. You can force a rebuild of your

application by deleting the “bin” folder and then replace it with a copy or by running the “aspnet_compiler.exe”

command with the “-c” switch. You can find the command by first browsing to the folder

“%SystemRoot%\Microsoft.NET\Framework\” and then open the v2.0.xxxxx subfolder (the numbers after v2.0

can vary) . Here is an example using the virtual directory name of the application: aspnet_compiler –v

/MyApp -c

19. FIPS Compliance

If your Silverlight application will be served from a web server on which FIPS (Federal Information Processing

Standards) compliance is enforced, you will need to make the following changes to both the web.config and

startup pages.

In the web.config, you must set debug to false when FIPS is enabled. This is true even during development: you

cannot set debug to true with FIPS enabled!

XML

<system.web>

<!--

Set compilation debug="true" to insert debugging

symbols into the compiled page. Because this

affects performance, set this value to true only

during development.

-->

<compilation debug="false">

In the startup page (normally default.aspx), you cannot use <asp:ScriptManager> or any controls that rely on it

since it generates a FIPS error. Therefore, you need to use html or javascript to start the Silverlight application.

The example below is using html which will work in most non-IE browsers such as Firefox:

XML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<title>DevForceSilverlightApp</title>

<style type="text/css">

html, body {

height: 100%;

overflow: auto;

Page 550: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Troubleshooting

550 | P a g e

}

body {

padding: 0;

margin: 0;

}

</style>

</head>

<body>

<object data="data:application/x-silverlight," type="application/x-

silverlight">

<param name="source" value="ClientBin/DevForceSilverlightApp.xap" />

</object>

</body>

</html>

The value in red is the location of your xap file relative to the location of the startup page. If you use an .html

page(ex: index.html) instead of an .aspx page, you will need to delete:

XML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

in order to be compatible with Firefox.

20. How to tell if the BOS is running.

You‟ve received an error message from your client application stating, “The remote server returned an error:

Not Found”. This is a communications error which occurs when the WCF client application is unable to

complete a handshake with the server. There are, unfortunately, a myriad of reasons why this might occur, but

one of the first things to check is if the service is actually running.

You can do this easily: open the web browser and point it to the URL which the client application is using. For

example, if the client app.config contains this...

C#

<objectServer isDistributed="true"

remoteBaseURL="http://localhost"

serverPort="9009"

serviceName="EntityService.svc" />

...then open the web browser to http://localhost:9009/EntityService.svc. If the service is running, you will see a

“Service description” page generated by WCF. If, instead, you see a page showing error information, then you

know the service cannot be started and that your application will be unable to run. Usually the error message on

the page has helpful diagnostic information.

21. Known Issues (Silverlight-Only)

The Copy Local property on DevForce references in the web project must be set to true for the apps to run

properly. This setting is required to allow the DevForce WCF services (defined in the *.svc files) to be

compiled correctly. If not set, the services will not start, the client application will be unable to connect to the

server, and you will see an error message as follows:

Page 551: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Troubleshooting

551 | P a g e

The remote server returned an error: NotFound. If the service is unavailable,

then also make sure that the endpoint bindings match between client and server.

When you begin your Silverlight solution using the DevForce Silverlight Application project template, several

DevForce assemblies are added as references to the web project; and for all, CopyLocal is set to true. However,

if you manually add or modify references, you may see that the property is initially set to false (which is the

Visual Studio default). Always check this property when you see the above error.

Contacting Support

Self Help Resources

DevForce installs with a number of resources for learning about Enterprise Application Development with

DevForce. There are manuals, guides, tutorials, code samples, and some movies.

Everyone has access to our forum (www.ideablade.com/forum) and the self-help features of our web-site

www.ideablade.com (see the Resources and Support menus there).

We are always updating the our site so please visit our support page for the most recent

news,

releases,

fixes,

help file(s)

tutorials,

videos,

reference applications and sample code, and

other support documents.

Paid Support

If you have an active Support Subscription, you can also submit a direct support case via our web site at

http://www.ideablade.com/CustomerSupportRequestForm.aspx

When submitting a support case, please try to simplify and isolate your problem as much as possible. If you are

submitting a bug report, creating a simple test case using the NorthwindIB database will help us to quickly identify,

correct, and test the issue.

IdeaBlade also offers Enterprise Support options as well as a full range of Professional Services to help you build

your application. Please contact your sales representative or email [email protected] for more information.

If you have concerns about our support or need immediate assistance and you cannot reach one of our support staff,

please call your sales representative.

Identifying your DevForce version

From within Visual Studio

An easy way to identify your DevForce version if you‟re already in Visual Studio is to look at the “About” box of

the “DevForce Object Mapper”.

Launch the DevForce Object Mapper from the Visual Studio Tools menu.

Page 552: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Troubleshooting

552 | P a g e

Select “Help ► About” from the Object Mapper menu.

You should see a dialog such as the one at right:

Of course you can‟t do this if you‟re

having trouble launching the Object

Mapper. As an alternative, you can

view the version number by examining

the property sheet of any referenced

“IdeaBlade” assembly.

Open the Solution Explorer

[Ctrl+W+S].

Open the References folder of

your Model or UI project (VB

developers must press the “Show

All” button first).

Select an “IdeaBlade” assembly.

Open its property sheet

[Ctrl+W+P].

Examine the “Version” property

near the bottom.

Outside Visual Studio

A handy and easy alternative is to examine one of the DevForce components in the installation directory which is

typically located at C:\Program Files\IdeaBlade DevForce.

Hover the mouse over IdeaBlade.EntityModel.dll:

Page 553: IdeaBlade DevForce Developers Guide 5.2.5

IdeaBlade DevForce Troubleshooting

553 | P a g e

Upgrading Your Software

We encourage you to upgrade your copy of DevForce to the latest version. The Installation Guide and Release Notes

provide detailed information about the versions and what steps (if any) you need to take to upgrade your software.