Professional Documents
Culture Documents
DevForce
Developer Guide
Version 5.2.5
IdeaBlade DevForce Developers Guide Contents
Contents
DevForce, Enterprise Applications, and the ADO.NET Entity Framework .............................. 8
The Problem ..........................................................................................................................................................9
Object Mapping Technology ............................................................................................................................... 10
The Microsoft ADO.NET Entity Framework ...................................................................................................... 10
Using DevForce with the Entity Framework ...................................................................................... 13
Advantages of Using DevForce ........................................................................................................................... 14
DevForce in More Detail ....................................................................................................................... 16
Advantages of Using DevForce (Revisited) ........................................................................................................ 16
More DevForce Advantages ................................................................................................................................ 28
Conclusion .............................................................................................................................................. 34
Getting Started.............................................................................................................................. 35
Installation ........................................................................................................................................................... 35
DevForce Start Menu ........................................................................................................................................... 35
The “NorthwindIB" database .............................................................................................................. 37
Development Process ............................................................................................................................. 37
Hello, DevForce ........................................................................................................................... 41
DevForce Application Architecture - The Big Picture ........................................................................................ 41
DevForce and the ADO.NET EntityModel ......................................................................................................... 42
Your First DevForce Application: a Walk-Through ............................................................................................ 45
Building the Domain Model ................................................................................................................................ 45
Add a User Interface ............................................................................................................................................ 66
Add Unit Tests ..................................................................................................................................................... 68
Add a WinForm UI .............................................................................................................................................. 75
Understanding the App.Configs ........................................................................................................... 89
Information Flow Between the App.Configs ....................................................................................................... 91
Monitoring Activity ............................................................................................................................... 92
Appendix: Listings of Sample App.Config Files ................................................................................................. 94
Appendix: Probing Sequence for the App.Config File ........................................................................................ 95
Class Libraries.............................................................................................................................. 96
Important Namespaces ......................................................................................................................... 96
The IdeaBlade.EntityModel.Entity ...................................................................................................... 97
Finding Help on DevForce .................................................................................................................. 100
XML Documentation ......................................................................................................................................... 100
IntelliSense ........................................................................................................................................................ 100
The Object Browser ........................................................................................................................................... 102
Class View ......................................................................................................................................................... 103
Class Diagram.................................................................................................................................................... 103
Business Object Mapping .......................................................................................................... 105
Introduction ......................................................................................................................................... 105
Overview of the ADO.NET Entity Model ......................................................................................................... 106
Working with the IdeaBlade DevForce Object Mapper .................................................................................... 106
Object Mapper Walk-Through .......................................................................................................... 106
2|P age
IdeaBlade DevForce Developers Guide Contents
3|P age
IdeaBlade DevForce Developers Guide Contents
4|P age
IdeaBlade DevForce Developers Guide Contents
Example of a Client-Side Class Containing Extension Methods for the EntityManager ................................... 320
Obtaining an EntityAspect Property on Your POCO Object ............................................................................. 321
Data Contract Serializer (DCS) versus .NET Data Contract Serializer (NDCS) .............................................. 322
POCO Save mechanisms ................................................................................................................................... 326
Summary – Things to Remember When Using POCOs in Your DevForce App .............................................. 329
Validation Through Verification ............................................................................................... 330
DevForce Verification ....................................................................................................................................... 331
Getting Started .................................................................................................................................... 332
Validation-Related Settings In the Object Mapper ............................................................................................ 332
Generated Property Code ................................................................................................................................... 334
Impact of Verifiers on the User Interface – A Caution ...................................................................................... 338
Now That You‟ve Been Initiated (and Before We Enter the Forest): A Quick Overview of the
Mechanics............................................................................................................................................. 339
Verification Types Overview .............................................................................................................. 340
Main Verification Classes.................................................................................................................................. 340
Verifiers ............................................................................................................................................................. 341
VerifierResult .................................................................................................................................................... 344
Triggers.............................................................................................................................................................. 347
VerifierEngine ................................................................................................................................................... 348
PropertyValueVerifiers ...................................................................................................................................... 350
Verification Deep Dive ........................................................................................................................ 355
Verifiers ............................................................................................................................................................. 355
Verifier Result ................................................................................................................................................... 359
Triggers.............................................................................................................................................................. 362
VerifierEngine ................................................................................................................................................... 370
Invoking Verification .......................................................................................................................... 375
Instance Verification .......................................................................................................................................... 376
Trigger Verification: Preset and Postset ............................................................................................................ 377
Monitor Execution with the VerifierBatchInterceptor ....................................................................................... 381
Verification and WinForms User Interfaces ..................................................................................... 382
UI Lockup .......................................................................................................................................................... 382
Improving the User‟s Experience ...................................................................................................................... 384
DevForce Silverlight Apps ......................................................................................................... 386
Overview - What is DevForce Silverlight? ........................................................................................................ 386
Creating a DevForce Silverlight Application .................................................................................................... 386
Silverlight Deployment Steps ............................................................................................................................ 387
Questions and Answers...................................................................................................................................... 387
Troubleshooting ................................................................................................................................................. 389
WinForm User Interfaces .......................................................................................................... 393
UI Data Binding ................................................................................................................................... 394
NET Data Binding ............................................................................................................................................. 394
NET v. DevForce WinClient UI Data Binding for WinForms .......................................................................... 395
Data Binding with DevForce WinClient UI Designers For WinForms ............................................................. 397
DevForce WinClient Data Binding Architecture ............................................................................................... 399
Nested Property Paths ........................................................................................................................................ 413
Data Binding to Data Objects of Any Type ....................................................................................................... 418
When to Use .NET Data Binding Instead .......................................................................................................... 421
When Not to Use Data Binding at All ............................................................................................................... 422
UI Architecture .................................................................................................................................... 423
5|P age
IdeaBlade DevForce Developers Guide Contents
6|P age
IdeaBlade DevForce Developers Guide Contents
7|P age
IdeaBlade DevForce DevForce and the Entity Framework
DevForce is a framework for building and operating multi-tier, data-driven enterprise applications.
By “enterprise application” we do not mean simply a big application, or an application for a big company. Rather,
we refer to an application with the following specific characteristics:
Its users devote many hours to its use, performing task essential to conducting the organization‟s business.
It requires a rich and responsive graphical user interface, dense with sophisticated controls
User interactions are complex; task and context switching is common.
It presents data that are complex in themselves, and deeply interrelated.
The data are stored centrally and shared with other users.
Supply chain, customer relationship management (CRM), and asset tracking applications are typical examples.
User productivity is critical. That puts a premium on the application‟s ability to provide a highly responsive, richly
featured user experience – the kind of experience typical of a desktop application running directly on a client
machine.
We expect people to get work done at any time from anywhere. Those people may be employees or they may be
valued partners. In either case, security matters. Accordingly, we often need to deploy and operate enterprise
applications over a wide area network – preferably over the internet – with undiminished productivity and security.
DevForce is especially suited to building and running applications that require a rich user experience delivered to
remote, Internet-connected clients.
While DevForce contributes at all levels of the enterprise application architecture stack, its Object Relational
Mapping (ORM) technologies and object-oriented approach to data management draw most of the attention.
Microsoft has stepped into this arena with the Language Integrated Query (LINQ) and the ADO.NET Entity
Framework, both released with version 3.5 of the .NET framework. The Entity Framework is a robust ORM
solution; the developer can retrieve data as “entities” by writing “LINQ to Entities” statements in her preferred .NET
programming language.
DevForce delegates to the Entity Framework the mapping between object and relational database schemas, as well
as the database persistence operations (queries and saves). These are important and challenging tasks that the Entity
Framework handles well.
8|P age
IdeaBlade DevForce DevForce and the Entity Framework
There is much more to an application than how it handles raw data. There is the business object layer that
encapsulates the data and governs those data with business rules. There are higher layers that address the application
workflow and user experience. All of this is outside the purview of the Entity Framework.
If we concentrate only on data management, we still find enterprise application requirements untouched by the
Entity Framework. Chief among them are:
Central services, Internet connectivity, distributed transactions, performance, security, scalability, and
Silverlight support - needs best met with an intelligent middle-tier server.
Highly responsive client UI‟s that exploit caching to avoid redundant, slow trips across the wire.
Object models mapped to multiple data repositories
Objects mapped to Web and WCF service data sources
Proper support for a business object layer with business rules.
DevForce satisfies these requirements even as it relies on the ADO.NET Entity Framework for basic ORM and query
facilities. The key components of DevForce include:
the Entity Manager, which includes a queryable client-side cache;
the Business Object Server (BOS) for services in a middle tier;
a provider for the LINQ language that permits LINQ queries to be used with both the client-side cache and
remote data sources
the Object Mapper which extends the ADO.NET Entity Framework designer and generates DevForce
entity code.
This chapter explores the key data management issues for .NET enterprise application developers. It introduces the
LINQ and the ADO.NET Entity Framework, explaining what they do and where they leave off. It then describes
how DevForce fills in the critical gaps.
The Problem
Every business application is an extended dialogue between a user and the business objects that fulfill the
application‟s purpose. Those business objects are behavioral objects first and foremost. They are the embodiment of
the customer stories that describe what the application does and how it does it.
A few behaviors may be stateless; financial calculations come to mind. But there is usually data somewhere in those
business objects. An order has a customer and a delivery date and line items describing quantities of goods sold for a
price. There is no escaping the data aspect of business objects and all of that data must be managed.
While the application is running, the data are held in session in some form. In an object-oriented system they are
held in fields and exposed as properties of a class instance. But because the data are long-lived – longer-lived than
any one session – they have to be saved between sessions. And because we share our data with others, we have to
save the data in permanent storage accessible over a network. Shuttling data between storage and the application
session is one of those necessary but “dirty” jobs, a job completely unrelated to the application‟s purpose.
Developers long ago discovered three data management problems.
First, the way we store data is not the way we use data in an application. Money, for example, is both an amount and
a currency (dollars, euros). The two aspects require separate slots in storage; from the application perspective, it‟s
just one thing: money. An “order” in the context of an application session may be seen as one “thing” with a
customer, a shipper, line items, etc. When we store that order in a relational database, the order, customer, shipper
and line are five different things. So the best representation of stored data often is not the best representation for
session data.
Second, session data are governed by rules. We must know the customer for an order before we can deliver the
ordered goods. The date of the order should precede the delivery date. Some other part of the application may need
9|P age
IdeaBlade DevForce DevForce and the Entity Framework
to be alerted when the order is actually delivered. The application is more maintainable and easier to understand
when the rules (behavior) and the data are bound together as “business objects” or “entities”. Such rules are largely
irrelevant when the data are tucked safely away in storage.
Third, there are many mechanical matters surrounding saving and retrieving data that have nothing to do with the
application‟s purpose such as opening and closing connections, composing SQL, detecting concurrency violations,
converting raw data into Data Transfer Objects, and managing transaction boundaries. Getting the application
dialogue right is hard enough without these distractions. Yes, the application still has to ask for data and stow them
away. But there should be a way to express our intent simply and entirely in terms of the application entities.
Ordinary operations should make no mention of databases, connections, tables, or columns.
The profound differences between stored data and session data lead developers to expend enormous energy moving
and translating between stored and session representations. This is wasted energy from the perspective of the
application customer who could not care less about our implementation problems.
It is also wasted energy from the developer‟s perspective because this problem has been solved by Object Mapping
technology.
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.
10 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
Technically, each file defines a .NET partial class. The compiler knits the two together, resulting in the
complete business object class.
Entity Persistence
The Entity Framework includes components responsible for moving business object data between the application
and the database.
11 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
The ObjectContext is the most visible of the components. The application uses ObjectContext to retrieve, hold, and
save entities. The ObjectContext maintains a cache of all the entities it manages. The developer writes queries and
submits them to the ObjectContext, which retrieves the selected entities and adds them to its cache before returning
them to the caller. The developer creates new objects and adds them to the ObjectContext. The ObjectContext tracks
changes – adds, modifications, deletes – to entities in its cache. A save command tells the ObjectContext to write the
changed entities to the database.
The Entity Framework handles all of these relational database persistence operations without troubling the developer
with details. The Entity Data Model and a few guiding parameters are all it needs.
LINQ to Entities
Earlier we described three problems for the developer who needs to represent data in the application as business
objects. The third problem was how to retrieve and save business objects using a language that hid the underlying
mechanisms and stayed true to the entity-oriented paradigm.
While the mechanics of saving business object data are challenging, it has never been difficult for developers to
express their intent. It is usually sufficient to tell some service class to “save” and the service knows what to do.
Getting data is a different story. It is not easy to say precisely which data you want, and in what form, using a
general purpose programming language. It‟s harder still to write queries in a strongly-typed manner and stay within
an entity-oriented paradigm. Until recently, object mapping vendors offered their own “object query languages”
(OQLs) which were, in fact, merely special purpose classes with strangled interfaces. OQL queries were clumsy to
write and repugnant to read.
With its release of the .NET 3.5 Framework, Microsoft added new language facilities for finding and accessing data
in a general purpose, object-oriented way, without exposing the details of data storage and retrieval. Chief among
the new features is LINQ, an abbreviation of Language Integrated Query.
A LINQ query looks much like an SQL query. Most programmers have long experience with SQL so, while SQL
itself may be tortured, most programmers are accustomed to it and find LINQ expressions familiar:
C# IQueryable<Product> products =
from prod in anObjectContext.Products
where prod.ReorderLevel > 100
select prod;
foreach (Product aProduct in products) {…}
VB
LINQ defines a set of query operators for interrogating arbitrary sources of data. Anything that can be enumerated
can be queried with a LINQ expression. We can use LINQ to select items from a list, nodes from an XML file, file
names from a file folder, or records from a database.
LINQ itself does not know how to do any of these things. LINQ defines the query operators and patterns for writing
query expressions. The operators and expressions are meaningless until they are married to an implementation that is
specific to a domain. Thus there is a LINQ implementation for querying in-memory objects (LINQ to Objects), an
implementation for querying XML structures (LINQ to XML), an implementation for querying relational databases
(LINQ to SQL), and so on. Microsoft provides some of these implementations but third parties can develop their
own and Microsoft encourages them to do so.
The LINQ facility provides the expressiveness we need for querying entities. What we need is a LINQ
implementation that supports an object mapping technology. Microsoft‟s LINQ to Entities is that implementation for
the Entity Framework.
12 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
Entity SQL
The Entity Framework supplements LINQ to Entities with its own query language called Entity SQL. Entity SQL is a
storage-independent dialect of SQL that works directly with the conceptual model. An Entity SQL query refers to
entities, properties, and associations (e.g. Order and Order_Customer) rather than the database elements in the
storage model. The particulars of data storage remain hidden in the object-oriented data design.
C# string queryString =
@"SELECT VALUE Product FROM Products “ +
AS Product WHERE Product.ReorderLevel > 100";
ObjectQuery<Product> products =
new ObjectQuery<Product>(queryString, anObjectContext);
VB
One significant drawback: Visual Studio will not detect even simple mistakes because the query string won‟t be
evaluated until runtime.
DevForce provides an alternative Entity Data Model editor, the DevForce Object Mapper, which is used for four
main reasons:
to augment the EDM schemas with DevForce-specific XML
to generate the DevForce business object classes which extend the Entity Framework classes
to work with a tabular interface that is more productive for larger (>20 class) object models
for more granular control over the generated class and property code
The Object Mapper plugs into Visual Studio and the developer can switch freely between the Object Mapper and the
Entity Framework designer, choosing the one that is most productive for the task at hand.
DevForce relies upon the Entity Framework for the persistence operations that target relational databases. The
Entity Framework prepares and issues the actual vendor SQL. The Entity Framework issues all insert, update and
delete commands and employs optimistic concurrency techniques to detect collisions between updates of the same
object by different users.
13 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
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.
14 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
Data access is the number one performance killer. Large volumes of data are deadly. Frequent trips to the server are
worse. And it‟s really bad if the UI freezes while waiting for data. Responsiveness and user productivity improve
dramatically when we eliminate unnecessary trips, reduce the size of data traveling over the wire, and retrieve data
asynchronously.
None of this is easy to implement. The ADO.NET Entity Framework is a purely 2-tier architecture in which the
client talks SQL to the database, a chatty conversation with few means to shrink the data. It doesn‟t remember
previous queries, we can‟t query its primitive entity cache, and we can‟t query asynchronously.
A DevForce application deployed in n-tier mode represents business object data in a compact form and compresses
the data before sending it resulting in smaller payloads over the wire. Smaller payloads, faster app.
Most applications ask for the same data over and over. DevForce has a query-able entity cache and a query cache.
We can ask the entity cache any question, including questions we‟ve never asked before. The query cache
remembers previous database queries so repeated questions don‟t cause redundant server visits.
In fact, we use DevForce to Entities, a LINQ-based query language, to pose questions that can search the cache,
search the data source, or search both as we wish.
Finally, DevForce offers asynchronous queries that can hide the actual cost of a remote query as perceived by the
end user. The UI continues to function and we can occupy the user‟s attention with an initial set of data while the
balance is retrieved in background.
15 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
The ADO.NET Entity Framework can only map entities to relational databases. DevForce can map business objects
to relational databases, web services, and WCF services, all in a single consolidated model.
Multi-Tier Applications
The ADO.NET Entity Framework is a client / server technology. It’s ObjectServices component, which is responsible
for querying and saving data to the database, executes in the same process as the client business object layer.
Database SQL commands and raw data flow over the wire.
This works just fine when there are relatively few clients, all connected to a secure, high speed LAN.
Performance becomes a serious problem when the traffic goes up or when going over a wide area network. There‟s a
lot of back-and-forth talk when SQL passes over the network and the data are verbose. With reduced bandwidth and
increased latency, those frequent roundtrips for data that no one noticed before become serious problems and the
user experience slows to a crawl.
Furthermore, in order for a two-tier application to work over the internet, you would have to expose your database
directly to the world. This opens up the possibility of someone stealing the connection string and browsing or
changing your database without authorization.
16 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
The DevForce n-tier solution, with its “Business Object Server” (BOS) deployed in a middle tier, overcomes all of
these obstacles.
The ADO.NET Entity Framework has relocated from the client arena to the Business Object Server where it now
functions purely as an object mapping technology, translating persistent data between entity and storage
representations. The client application hosts the DevForce Entity Manager, a component responsible for holding
business objects in cache and communicating with the BOS.
The business objects and the Entity Manager itself are completely decoupled from the ADO.NET Entity Framework.
There are no references on the client to any of the Entity Framework assemblies.
Nor do clients talk to the database. Instead, the Entity Manager sends commands to the BOS and receives business
objects in return.
Commands may be expressed in a variety of formats including the new LINQ to DevForce query language. The
BOS translates a LINQ to DevForce query into a LINQ to Entities query and submits it to the Entity Framework.
The Entity Framework returns simple entities to the BOS which forwards them to the client. DevForce on the client
turns them into business objects and caches them in the Entity Manager.
The BOS and the client DevForce Entity Manager exchange data in a serialized binary form that passes easily
through firewalls and over the Internet. The BOS compresses the data before sending them to the client. These
smaller payloads reduce network traffic and improve client performance.
The BOS is effectively stateless. It retains no essential information about client sessions between requests. Each
client request resolves to a method call running on a new thread; the call holds onto entity data just long enough to
fulfill the request after which it is discarded. Such statelessness makes it easy to distribute requests among multiple
BOS servers for scalability and fault tolerance.
Remote Services
Some applications require services that must execute in a centrally hosted environment, perhaps because they
involve proprietary logic or because they crunch volumes of data that would swamp the network if transmitted to
clients. A client can make a “remote service call” to the BOS, which will invoke custom server side methods to
perform or delegate these hosted services.
The BOS can watch for server-side events such as data updates or network notifications, and publish corresponding
events to subscribing clients through its “push” service.
17 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
DevForce Silverlight 1
Features described in the section are included with the DevForce Silverlight product.
Microsoft Silverlight enables deployment of .NET applications within a browser. There is no application to install,
no client footprint, and no compromise of the client machine‟s security. The door is open to deliver applications to
consumers and locked-down enterprise environments securely.
Data access remains a challenge. Data-driven Silverlight applications need access to the same data as their desktop
equivalents. A Silverlight application can only reach data resources over the Internet and, as we‟ve seen, the
ADO.NET Entity Framework cannot move data over the Internet. But a DevForce Silverlight application can.
In 2009, IdeaBlade will release “DevForce Silverlight” supporting SilverLight applications that are based on the
same rich object model deployed in DevForce WPF smart client applications.
In Summary
With the DevForce Silverlight product, you get all of the above capabilities in a tool that permits you to develop
Silverlight applications that use the Entity Framework in the same way that WPF Windows clients do.
Secure Services
The Entity Framework only supports a two tier architecture in which the client talks directly to the database. There
are not intrinsic capabilities for authenticating users, authorizing access, or encrypting data. This architecture relies
entirely on coarse grained network and database measures to secure the application and requires extra care to protect
the client machine from theft or intrusion.
This level of security is not good enough in many environments. There may be tough corporate or legal mandates to
protect sensitive data from unauthorized access. A client machine could fall into mischievous hands. Any .NET
program is easily disassembled. A determined malefactor could discover the client-side application security
measures, develop counter measures, and attempt unauthorized persistence operations.
Connection Security
The trouble begins with the database connection string. In a two-tier world, each client must provide the Entity
Framework ObjectContext with a database connection string before it can access the database. The database is easily
compromised if the string contains a user and password. Encrypting the string until the moment of use certainly
helps – if you remember to do so – but still amounts to security-by-obfuscation. It is much safer to rely on the
operating system to authenticate the user to the database via the Security Support Provider Interface (SSPI) as when
the MS SQL Server connection string specifies “Integrated Security=SSPI;”.
1
DevForce Silverlight and DevForce WinClient are separate IdeaBlade products. They are combined in the DevForce
Universal product.
18 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
Moreover, each database connection is unique, defeating the performance advantage of connection pooling.
This technique works but there are problems. The IT management burden grows heavy when there are many
application users scattered across a widespread corporate network. New users must be added both to network
directories and to the database‟s own list of authorized users. Departing employees should be removed from all
directories. The application administrator rarely maintains the network and database logons so there are
communications breakdowns that lead to mistakes.
In a DevForce n-tier deployment, the Business Object Server (BOS) stands between the client and the database.
The client must login to the BOS before the BOS makes any requests on the client‟s behalf. After login every
transmission from client to server is accompanied by an encrypted session token that identifies the client.
NT Authentication and impersonation are viable alternatives for LAN users and can be combined with
alternative login mechanisms when users access the application from outside the corporate network.
Clients no longer access the database directly. They don‟t hold a connection string nor issue vendor SQL calls. They
don‟t know where the data physically reside.
Instead they ask the BOS to fetch and save data on their behalf and only commands and object data travel over the
wire. The BOS, running on a secure machine, connects to the database with its own private connection string. The
BOS performs all database operations.
Authorization
The ADO.NET Entity Framework has no authorization mechanisms. In most cases, the application relies upon
authorization settings in the database – settings which operate crudely at table levels and do not reflect more detailed
business rules. Application-specific authorizations can only be enforced in the client. The ability to limit order
approval or restrict access to a patient record depends entirely on business logic executing in the client.
With the DevForce BOS in place every query and save operation is subject to inspection. The BOS invariably calls
certain customizable secured operation methods, passing along the client‟s Principal so each method can identify
the client user and his assigned roles. The method can determine if the user is allowed to perform the requested
operation and what action to take if permission is denied.
Every step in this process, from login to security check can be tailored to meet the particular needs of the
application. There is nothing that client can do to thwart these measures. The BOS will execute them like clockwork
and the client has no access to the server, no ability to inject malicious code.
Encryption
The developer is free to engage the kind of encryption that is most appropriate. SSL is typical but other methods can
be inserted in the pipeline. DevForce prefers to use Microsoft Windows Communication Foundation (WCF) for
client-to-server communications; the WCF security-related configuration options are all available.
19 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
object. Maybe the entity doesn‟t exist. Maybe it just hasn‟t been retrieved yet. The Entity Framework can‟t tell.
We‟d have to query for the object – or explicitly load it – to be sure.
Unfortunately, we can‟t simply query the Entity Framework cache directly. All Entity Framework queries (and
loads) reach across the net to the database – even repeat queries issued mere moments ago.
Do applications ask the same question twice? Yes they do. Users are always cycling among several active tasks;
each time they return to a task underway, the application re-issues a query. The developer might take pains to cache
such queries herself. But that‟s an arduous and error prone pursuit best left to the DevForce framework.
20 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
We can make the application appear extremely fast by combining a Take() query that requests a small set of data
with an asynchronous query that requests a larger set. Suppose, for example, that the user requests several thousand
orders. We don‟t know for sure he‟ll do so, but we‟ve seen it before. So we take defensive measures.
We first compose the user‟s order query in the usual manner. We then suffix it with a call to Take() that limits the
request to a safe maximum of 3,000 orders. We submit this one as an asynchronous query because we know from
experience that it will take several uncomfortable seconds to return.
We follow immediately with the same query, also suffixed with a call to Take(), this time limited to 100 orders. This
one we submit synchronously2; we‟re willing to wait a half second for this one. It returns as a list and we present the
first 100 orders. The original request for 3,000 eventually arrives; the call-back method fills the list. On screen, the
order grid magically grows from 100 to 3,000. The user is delighted.
Note that there is also a Skip() extension method that can be used if you want something other than the first n
members of an ordered result set. The following query will bring down the next 100 customers:
2
In Silverlight applications all queries must be asynchronous, so in that case we will have to do both of our queries – the larger
one and the smaller one – asynchronously. In a Silverlight app, we might choose to tie up the user interface of our application
by other means (such as displaying a child window) while waiting for the smaller query to return.
21 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
of twenty columns could yield a couple of pages of code. The slightest change to the storage schema necessitates a
revision of this code.
We won‟t do it without adequate tools and code generation. We would simply lack the patience and discipline.
Weaknesses
The Entity Framework designer and generator fall short in several critical respects:
The designer does not give the developer adequate control over the generated code
The generated properties are not adequately extensible, limiting the developer‟s ability to abstract out the
business logic shared across business objects.
The code generator blocks introduction of “base” classes into the inheritance hierarchy, limiting the
developer‟s ability to inherit common business object behavior.
Designer Woes
We could write the properties by hand. But we‟d like to use the Entity Framework Designer to generate the code for
us so that the property code conforms to standard and includes all the property interceptor calls it should have.
Unfortunately, the Entity Framework Designer won‟t generate entity code without validating the storage model and
the mapping schema – which don‟t yet exist.
The DevForce Object Mapper can generate the tedious persistent data accessor property code with the conceptual
model alone. It doesn‟t need the storage or mapping schemas which we can fill in later.
The developer should be able to build the conceptual data model without first committing to a storage or mapping
specification. When the developer determines that the application data requirements are sufficiently well known to
warrant database schema design, she can add the storage and mapping schemas “just in time.”
Unfortunately, the EF insists that every conceptual entity be mapped. It refuses to “validate” the model when there is
no mapping and it won‟t generate code for an un-validated model.
The developer should work on just those business objects that are pertinent to the user story. Who cares if the
database has hundreds of tables when we only need five business objects. Sadly, the EF designer is unforgiving.
There is no going back once the developer has selected his tables and generated her model. She will have to edit the
XML to add the sixth, seventh, and eighth objects.
22 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
Indeed, there are a great number of everyday mapping activities that can only be accomplished by dipping into the
raw XML.
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.
23 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
We are out of luck if we need generalized property logic that works across multiple properties. We shouldn‟t have to
manually implement an On…Changing or On…Changed method for every property we want to validate. We should
have a model-wide solution to validating changes that centralizes validation rules and manages them as resources …
as we do in DevForce. And remember: validation is but one example of logic we could manage as metadata and
introduce dynamically into the property.
Missing Inheritance
The Entity Framework supports inheritance hierarchies but only if each class in the hierarchy is mapped to a
physical database table. The only base class that isn‟t mapped is the Entity Framework‟s own Entity class.
There is no room to insert a class into the hierarchy that provides pure behavior. This is a serious omission. Years of
real world application building confirm the wisdom and necessity of at least one base class that provides behavior
that all business objects have in common. This is the application model base class, not Microsoft‟s or IdeaBlade‟s.
Such a class could
Manage persistent auditing fields such as LastModifiedBy and LastModifiedDate.
Generate separate audit trail objects during save.
Implement data binding interfaces such as IDataErrorInfo.
Cache broken validation rules.
Provide access to the application‟s Dependency Injection or Service Locator facilities.
It is not uncommon to introduce similar classes elsewhere in the hierarchy. We might want an Inventory class in
support of several distinct types of inventory, each mapped to its own table; we shouldn‟t have to have an Inventory
table too.
Like the Entity Framework, DevForce generates a partial class file covering the persistent data, leaving the
developer to write custom business logic in a companion file. But DevForce gives the developer better control over
the generated code. For example, using the DevForce Object Mapper she can
Decide which properties to make public and which to hide
Make any property read only
Include or exclude DevForce value verification
Impose a required-value requirement on a property mapped to a nullable column
24 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
Property Interceptors
DevForce provides a mechanism to intercept and either modify or extend the behavior of any .NET property. This
interception is intended to replace, and expand upon, the technique of marking properties as virtual and overriding
them in a subclass. This facility is a lightweight form of what is termed “Aspect-Oriented Programming”.
Interception can be accomplished either statically, via attributes on developer-defined interception methods, or
dynamically, via runtime calls to the „current‟ instance of a PropertyInterceptorManager. Attribute interception is
substantially easier to write and should be the default choice in most cases.
You can learn about property interceptors in the chapter “Property Interceptors” in this Developer Guide.
25 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
We see that DevForce is relying on the Entity Framework for object mapping and persistence operations while
shielding the developer from unpleasant implementation complexities.
The critical factor is the introduction of the DevForce business object model as a construct separate from the Entity
Framework‟s own conceptual data model. In effect, DevForce provides a higher level abstraction over the Entity
Framework object mapping abstraction.
Entities of a DevForce conceptual model can be mapped to Web and WCF Service methods as illustrated here:
26 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
automatically store a user‟s pending changes locally every few minutes “just in case.” If the application crashes or
the battery dies, the user could re-launch later and recover her work.
We use this same mechanism to develop applications that operate offline intentionally. The user pre-loads the cache,
preserves the cache locally, shuts down, re-launches while disconnected, does work, saves that work locally, and
finally saves the pending changes to the database when reconnected.
Someone may have saved changes to the same data while this user was offline. It happens while online too but the
risk is greater when the time from change to save is prolonged. The response is essentially the same: DevForce
detects the concurrency violation and the application resolves it, perhaps with the user‟s help.
27 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
28 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
The same navigation property if generated by the Entity Framework would have returned null.
Null values greatly complicate the developer‟s life. She has to be on constant alert for null reference exceptions.
Data binding to a property that can return null is pure hell. A null reference exception thrown during data binding
results in an ugly red bullet on screen and an error message that baffles the poor user.
A customer null entity has all the properties of a real customer. The programmer can distinguish a null entity from
the real thing when she has to but she doesn‟t have to litter the code with null value tests. Data binding survives
nicely; a UI widget bound to a null entity displays a conveniently vacant value of the developer‟s choosing.
The null entity pattern spares developers many hours of pain both in writing and reading code.
Entity Metadata
The developer sometimes needs to know aspects of the conceptual data model itself. For example, she might need to
iterate over all the child relationships of an order without knowing what those relationships are in advance.
The metadata about such features of the model are often hard or impossible to find in the Entity Framework.
DevForce records these features in metadata objects that can be easily reached programmatically through the
29 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
EntityMetadataStore class. See the section “Getting Information About an Entity Type with GetEntityMeta()” in the
Object Persistence chapter for detail.
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.
30 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
By default, DevForce looks for the connection string in an XML configuration file, expecting to find a
dataSourceKey node identified by the DataSourceKey name. The connection string should be an element within
that node. Continuing our first example, we might locate any one of four connection strings depending upon the
environment.
We don‟t want four separate configuration files. So instead, DevForce lets us maintain multiple connection strings
for each DataSourceKey. It differentiates among them by means of a DataSourceKeyExtension, an extra bit of string
associated with the DataSourceKey name. Now we can record as many connection strings as we need for any
conceptual data source by creating distinct nodes uniquely identified by the both key name and extension. Nodes
that share the same key name refer to the same conceptual data source; the extension tells us which concrete data
source to use at runtime.
We control runtime behavior by telling the client-side Entity Manager which extension to use. If we‟re running in
the “QA” environment, we‟ll specify a “QA” extension. If the application entities map to conceptual databases
“Alpha” and “Beta”, the application will connect to the concrete databases identified by “Alpha_QA” and
“Beta_QA”. When we run in production we switch to the “Prod” extension and the application now connects to
databases identified by “Alpha_Prod” and “Beta_Prod”.
Notice that databases travel in sets. There is the “QA” set and the “Prod” set. We can use this same technique to
support multi-tenant applications that store customer data in separate databases – an approach often mandated by
financial clients. An “Acme” client session runs against the “Alpha_Acme” and “Beta_Acme” databases. The
“Baker” client runs against the “Alpha_Baker” and “Beta_Baker” databases.
The DevForce configuration file may not be the best place to store the connection information. In our second
“On Demand” scenario, we could be adding new application tenants frequently. Rather than update the
configuration file every time, we write a DataSourceKeyResolver to calculate and locate connection information
based on key name and extension.
31 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
DevForce supports the StoreGeneratedPattern=”Identity” setting, extending its capabilities to encompass entities in
the DevForce client-side cache. These entities need primary key values immediately upon creation, though they may
not be persisted until much later. DevForce gives such entities a temporary primary key upon creation so they can be
referenced client-side without any trip to the data source. Upon saving, their value is updated in the client-side
cache to the value generated on the server. The foreign key values in other entities that reference the targeted entity
are also updated to reflect the new, server-generated primary key value of the target entity.
The Entity Framework can generate the new key for you if the key is a single valued integer key mapped to a SQL
Server identity column. The EF can‟t set the object‟s permanent key; that won‟t happen until the newly created
object is saved and even then it will be the database, not the application, that determines the key. So the EF assigns a
temporary key and refers to that key when it adds related entities to the new object‟s graph.
For example, upon creating a new Order, the EF assigns it a temporary key (e.g., “-1”). When we add a new
OrderDetail to that Order, EF inserts “-1” into the hidden foreign key field of the OrderDetail that links the detail to
the parent order. When the application saves these new entities, the EF acquires the permanent ids from SQL Server
and updates the objects accordingly. Continuing our example, the EF learns that the new Order‟s primary key is
“123” and updates the order‟s id.
It also takes a critical second step: it finds all associated OrderDetails and updates their “ParentOrderId” column
values from “-1” to “123”. “Id Fix-up” is our name for this propagation of permanent ids to related objects. Only
then does it try to save the fixed-up OrderDetails.
32 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
The Entity Framework implements optimistic concurrency by comparing the value of a concurrency column in the
pending record with the value of that column in the stored record. If the values are the same, the pending record can
be saved. If the values are different, the pending record is out of sync with the stored record; the framework assumes
a concurrency conflict and throws the exception.
This technique works so long as the concurrency value is changed after each successful save. Who is responsible for
that change? The Entity Framework says that you are.
You are fortunate if the database table has an update trigger that can do it. Otherwise, you have to write the code that
updates the concurrency column and you have to remember to call it at the right moment.
DevForce can handle the concurrency column update for you. In the DevForce Object Mapper you declare the
concurrency column (or columns) and pick a method from a list of concurrency column update methods. DevForce
will call that method at the appropriate time. Yes, you can extend the list with a custom method.
Sandbox Editors
Sandbox editors are a convenient alternative – or compliment – to checkpointing. Imagine that customer “Jim” calls
to adjust one of his orders. You find the order in the list and open it in an editor and begin working on it. You‟re in
the midst of changing deliver addresses, order items, billing information, etc.
Suddenly, premium customer “Sally” calls you with an urgent request for a new order that you must enter right now.
Jim kindly agrees to complete his changes later. You begin Sally‟s order in a second order editor.
You are half way through Sally‟s order when Jim calls you back. He says “never mind, that order we were changing
is just fine the way it was.” You switch briefly over to Jim‟s order and discard all changes simply by shutting down
the order editor. You return to Sally‟s order editor, complete it, and save.
There are two distinct orders in flight in this example. Each has its own set of entities some of which may overlap
(e.g., the list of shippers) although most do not. With DevForce, you can create separate Entity Managers – with
separate caches – and maintain these editor sets separately, each in their own “sandbox”. The entities in the “Jim”
Entity Manager are isolated from the entities in the “Sally” Entity Manager and all of these entities are isolated from
the list of orders held in the application‟s main Entity Manager.
Now imagine that this scenario takes place off line. There is no access to the database. That still works in DevForce
because you can easily pass copies of entities from one manager to the next without going to the database. You
might even prefer this approach when connected if the performance of your application is at a premium and
bandwidth is poor.
Managed Lists
Keeping lists of entities up-to-date is a recurring application problem. It‟s the holiday season as I write this so let‟s
imagine we‟ve written Santa‟s inventory tracker. The tracker displays a list of undelivered packages on Santa‟s
dashboard. As each package finds its intended child, the list should grow shorter.
33 | P a g e
IdeaBlade DevForce DevForce and the Entity Framework
An elf in the back of the sleigh is updating package information on a separate screen, marking each one “delivered”
as it drops down the chimney. Santa sees the same dashboard on the console monitor because he and the elf are
cabled together.
What makes the list shrink when the elf marks the package delivered? Traditionally, we‟d have written the logic
ourselves. But there is a problem: the elf‟s module doesn‟t know about the list displayed on the dashboard. So it‟s
not as easy as remembering to remove an item from UndeliveredList when the elf clicks the “Delivered” button.
We‟ll probably need some kind of cross module event scheme.
DevForce can handle this for us automatically with its managed list feature. Let the two modules share the same
Entity Manager, let the list be governed by this manager, give the list the appropriate predicate – “keep item if not
delivered”- and the list takes care of itself.
Conclusion
IdeaBlade has been in this arena since the early days of .NET. The DevForce product has long offered most of the
capabilities described in this paper including the multi-tier ORM, client-side caching, and code generation.
The Microsoft ADO.NET Entity Framework is a solid contribution to the field and its very existence confirms the
widespread need for an infrastructure like DevForce. But the Entity Framework by itself cannot fulfill the needs of
many enterprise applications. The productivity isn‟t quite there. The generated code lacks essential support for
business object development. Its two-tier architecture limits the application‟s ability to reach a distributed user
community with the required performance and security.
With DevForce, developers can quickly realize the potential of an object-oriented, multi-tier, enterprise application
connecting hundreds or thousands of users.
34 | P a g e
IdeaBlade DevForce Getting Started
Getting Started
Getting Started
Installation
DevForce Start Menu
The “NorthwindIB" database
Development Process
This section offers a brief overview of how to get started with IdeaBlade. Topics covered in this chapter are:
Topic Description
Installation Brief introduction and pointer to pertinent sources.
DevForce Start Menu Tools and information accessible from the Windows Start Menu.
NorthwindIB database Many examples make use of the tutorial NorthwindIB database, which is
based on Microsoft's blueprint NorthwindEF database.
Documentation Best Practices indicators and typographical conventions used in this guide.
Conventions
Installation
The separate DevForce Installation Guide covers the installation and upgrade process in detail and also contains a
troubleshooting section. The DevForce Release Notes contain version specific information that you may need for
certain upgrades.
35 | P a g e
IdeaBlade DevForce Getting Started
Documentation
Description
Menu Item
Developers Guide The document you‟re reading now.
DevForce Help The technical help covering the DevForce assemblies, types, and type
members.
Installation Guide How to install and upgrade DevForce. Includes Troubleshooting tips.
Learning Units Scripts and solutions for hands-on walk-thrus of DevForce product features
and applications.
Release Notes Documentation of new features, enhancements to existing features, bug fixes,
upgrade issues, and everything else you need to know when upgrading your
copy of DevForce.
36 | P a g e
IdeaBlade DevForce Getting Started
Applies to
Tools Menu Item Description DevForce
Silverlight
Assembly Binding Discovers third-party control suites on your machine; compares the No
Redirector names and version numbers of the associated DLLs with the versions
supported by DevForce; suggests redirections to permit you to use your
installed versions with DevForce; and writes machine.config statements
to implement the selected redirections.
Config Editor Edits an IdeaBlade.ibconfig file governing your application‟s Yes
deployment. DevForce application deployment is its own chapter in this
guide.
Database Installer Installs the NorthwindIB sample database. This is a SQL Server Yes
database (not a database server) that is used in many of the Learning
Units that accompany the product.
N-Tier Quick „n dirty tool that facilitates testing a distributed app. Creates No
Configuration folders for client- and server-side assemblies and configuration files,
Starter and otherwise facilitates running the DevForce EntityService in a
separate process from the client side app and EntityManager.
Product Key Facilitates replacing your current product key with a new one (in the Yes
Updater case of upgrade, etc.)
Tool Box Installer When you elect installation of Windows Forms support (the default), No
DevForce adds a number of visual design components to the Visual
Studio 2008 Tool Box. Sometimes VS won‟t accept our automated
attempt to install these tools. You may remove one or more from the VS
tools accidentally. You may acquire a 3 rd party control suite for which
we have a dedicated visual component. This installer will help you
(re)install these components.
Trace Viewer Tool for listening to logged activity from a running DevForce Yes
application.
Development Process
DevForce promotes a distinctive process for development of distributed, object-oriented, enterprise applications.
37 | P a g e
IdeaBlade DevForce Getting Started
The “object-oriented” and “distributed” parts may seem a little foreign to some.
The object-oriented approach to data means thinking in terms of Business Objects and Object Persistence rather than
retrieving, inserting and updating data records. This becomes so obvious and easy in DevForce that, in a few days,
you stop thinking in terms of fields and joins and you may even forget how to use ADO.
The “distributed” aspects don‟t surface until well down the road and, because it‟s easy to re-configure the
application for multiple physical tiers, there is no cost to delaying awareness of multi-tier considerations.
Many developers do the lion‟s share of their work in a one-tier physical model in which all components of the
system – even a test database – reside on a single physical machine. You may prefer to access test data on an
independent server in which case your development experience is not that much different than good-old client /
server.
The following sections summarize the stages in a typical development process. The summary highlights the end-to-
end influence of the DevForce infrastructure.
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.
38 | P a g e
IdeaBlade DevForce Getting Started
Business Object creation and comparison methods are next, following the IdeaBlade recommended patterns and
using a few simple methods of the Entity class at the root of all Business Objects.
Note that we‟re not writing UI here. We‟re exploring and enriching the business objects independently of any
particular user experience. Our changes go in the developer partial classes that are initially generated by the Object
Mapper and subsequently left entirely alone.
Slowly we begin to add rules and to verify those rules. The ship date must be after the order date, for example. Only
a user with administrative rights can change a salary. The developer inscribes these rules in the custom entity classes
that comprise the business logic layer.
The developer doesn‟t spend much time thinking about how to push and pull entities from their persistent homes in
data storage. That‟s the job of the EntityManager, guided by the object map. Developers no longer embed SQL
commands for reading or writing data to storage. Instead they invoke strongly-typed LINQ queries that return
business objects (entities). They write business logic that references object properties, not data fields.
Nonpersisted Classes
Business objects have state and long-lasting identity. They are stored (persisted) for an extended time.
Not everything is a business object. There are transient objects and Singleton classes with no state to store such as
temporary collections (e.g. list of user-selected products).
calculation classes (e.g., a ROI calulator).
helper classes to which similar Business Objects delegate common functionality (e.g., audit logging).
APIs to external applications.
Because these are not "Business Objects" - they do not carry state (or least what state they have is not stored in the
database). They are not mapped and they fall outside the purview of DevForce Object Persistence.
Such classes are written in the normal fashion and will be collected in one or more separate application projects.
Deploy
The application is completed in record time. There have been mock deployments to development, test, and staging
environments. The production deployment is no different.
The application code itself is identical, whether it is installed on the server or deployed to a client; the only
difference is the configuration of their respective IdeaBlade Configuration files which are now separately edited for
the production environment.
There are packages of files: a server package and a client package.
The server package typically is a Microsoft Install file (MSI). This file is unpacked into the designated directories of
one or more servers and each is launched. The Business Object Server monitors the health and activity of each
server application.
DevForce WinClient
.NET‟s “ClickOnce” publishing is the easy way to build and distribute the client package.
39 | P a g e
IdeaBlade DevForce Getting Started
ClickOnce puts the package (a collection of files) on a web server and makes it accessible through a web page. PC
users with .NET runtime installed navigate by browser to the page and clicking an install button.
ClickOnce downloads and installs the client application in the user‟s personal directory. It then launches
automatically. This install happens once. Developers upgrade the application and publish revised client packages.
ClickOnce detects the upgrade and downloads the new version seamlessly.
There is no danger of the application executables and class libraries colliding with those of other application. Nor
will an install (or uninstall) of a different application disturb this one. .NET has corrected the DLL nightmare with
proper versioning. .NET applications don't have to use the Windows registry either.
DevForce Silverlight
In a Silverlight application, the XAP file is your client package, and Visual Studio has fortunately built it for you.
You‟ll need to be sure to place the XAP file in the ClientBin folder of the web site which is hosting the application.
This site will typically also host the Business Object Server (deployed as the server package above) but this is not a
requirement. If you do host the BOS and Silverlight application from different web sites you‟ll need to be sure to
also deploy a file named “clientaccesspolicy.xml”. More information on this is available later in this document.
40 | P a g e
IdeaBlade DevForce Hello DevForce
Hello, DevForce
Hello, DevForce
DevForce Application Architecture - The Big Picture
DevForce and the ADO.NET EntityModel
Your First DevForce Application: a Walk-Through
Building the Domain Model
Add a User Interface
Add Unit Tests
Add a WinForm UI
Understanding the App.Configs
Information Flow Between the App.Configs
Monitoring Activity
Appendix: Listings of Sample App.Config Files
Appendix: Probing Sequence for the App.Config File
Creation means
finding the new world
in that first
fierce step
with no thought of return.
David Whyte, “Statue of Buddha”
Don‟t look back. All change, all creation, is attended first by grief for what is lost followed by the clarity in moving
on with no thought of return.
DevForce is not magic and you‟re unlikely to build an enterprise application over night. But you can build a good
application that you‟re proud of in reasonable time. Once you lay to rest your old habits and have grieved for them
awhile, the new path will embrace you and, in spare moments, you may wonder how you ever did it that old way.
“But I‟m so happy in my comfortable way. What if things go wrong? That DevForce thing is just a little
intimidating.”
This chapter should ease you across the threshold, highlighting some of the more prominent DevForce features
along the way.
41 | P a g e
IdeaBlade DevForce Hello DevForce
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.
42 | P a g e
IdeaBlade DevForce Hello DevForce
The developer‟s first step in building the object model for her application will consist in creating an entity model in
an EDMX file. Typically she will use the Visual Studio Entity Data Model wizard to create the initial version of the
EDMX file and the corresponding generated code file. After that, she will work with some combination of the
Visual Studio Entity Model Designer and direct XML coding in the EDMX file, depending upon her preferences
and whether she needs to use features in her model that are not supported by the Entity Model designer.
The second step will be to create a Domain model using the DevForce Object Mapper. This model is so named
because it will be composed of one or more Entity Models persisted in .EDMX files.
The DevForce Object Mapper will alter the .EDMX file by adding additional elements and attributes. These added
features are ignored, and left undisturbed by, the ADO.NET Entity Data Model Designer. Because of this, the
developer can move back and forth between the Visual Studio Entity Model Designer and the DevForce Object
Mapper without fear of either disturbing the other‟s work.
There is, by intent, some overlap in the the functionality of the DevForce Object Mapper and ADO.NET Entity Data
Model Designer. However, our intial work on the DevForce Object Mapper has been focussed on providing needed
or useful capabilities that are either not present, or are difficult to work with, in the Entity Data Model Designer. Our
goal is to make it as convenient as possible for you to work with your model.
We mentioned that the Entity Data Model wizard and designer, in addition to altering the .EDMX file, generate the
classes that comprise the compilable manifestation of the object model. From the Object Mapper‟s enhanced version
of the .EDMX DevForce generates two sets of classes. The first is essentially the same Entity Framework model
generated by the Visual Studio tools. This version of your object model will be deployed to the logical middle tier
of your application, where it is used by the ADO.NET Entity Framework for creating objects of the type that it
understands.
The second version of the object model generated by the DevForce Object Mapper is a DevForce version consisting
of business classes that inherit from IdeaBlade.EntityModel.Entity. As previously mentioned, we refer to this
version of the model as the Domain model. The Domain model is “persistence ignorant”: unlike the Entity
Framework model, it has no knowledge whatsoever of the back-end datastore or the mapping between that and its
objects. In an n-tier deployment, it is the only model that is deployed client side. The client needs no connection
information for back-end datasources.
The DevForce Domain Model is the only client-side model your application will use. It is, however, also deployed
server-side; and it‟s scope of operation is synonymous with the bracketed area labelled “DevForce Business
Objects” in Figure 1.
The Domain Model is a consumer of Entity Data Models, whose .edmx files typcially define the lion‟s share of its
content. Server-side, DevForce delegates to the Entity Framework the jobs of communicating with the database(s) to
perform persistence operations including data retrieval and saving. The Entity Framework, in turn, uses the
compiled versions of the Entity Data Models, as well as connection information typically stored in an app.config
file, to do its work.
In DevForce, all direct communications with back-end data sources are considered, logically, as server-side
operations, which they will literally be in an application deployed across three or more physical tiers. The
application components that facilitate such communications, including the Entity Framework, Entity Data Model,
and DevForce EntityServer are considered server-side components, and are kept logically separate from client-side
components such as the DevForce EntityManager and the client application. It is perfectly possible to deploy both
the logical client-side components and the logical server-side components to the client machine, and this is often the
configuration used for much of the development work even on enterprise applications.
When all application components including the database server are deployed on a single physical machine, you have
a “single-tier deployment”. When all application components except the database server are deployed on a single
physical machine, and the latter is deployed to a remote machine, you have what is known as a “client-server”
application. When client-side application components are deployed on a separate machine from server-side
application components, this is typically referred to as “n-tier” deployment, even if the database server resides on the
same machine as the application server (e.g., the DevForce BOS).
43 | P a g e
IdeaBlade DevForce Hello DevForce
However, since the strongest application security, widest availability, greatest scalability, and easiest deployment are
all associated with n-tier physical deployment, we figure it‟s much to your benefit to write your application from the
beginning to permit that, and we make it as easy for you as we can.
What are the Parts of Your Business Model, and Where Are the Parts Deployed?
The ADO.NET Entity Data Model and the DevForce Domain Model each have representations in both
XML and in .NET code.
The representation of the Entity Data Model (EDM) in XML is a file with the extension .EDMX. Visual
Studio includes a code generator that creates a corresponding file of .NET code. This file has the same
name as the .EDMX file, but an extension of “designer.cs”. It is stored by Visual Studio subordinate to the
.EDMX file in the Entity Data Model project.
The representation of the DevForce Domain Model in XML consists of a file with the extension .IBEDMX;
and one or more of the Entity Data Model (.EDMX) files just discussed. The .IBEDMX file mostly acts as
a catalog of the Entity Data Models that contain, in XML, the detailed specifications of entities, properties,
associations, tables, columns, relationships, and mappings. Both the DevForce Object Mapper and the
Visual Studio Entity Data Model Designer read from and write to the .EDMX files. The tools cooperate
completely, fully respecting each others‟ work, and may be used in any order.
Using the specifications stored in the .IBEDMX and .EDMX files, the DevForce Object Mapper generates
a file of .NET code which has the same name as the .IBEDMX file, but an extension of “designer.cs”. This
generated code file is stored by the Object Mapper subordinate to the .IBEDMX file in the Domain Model
project.
The Object Mapper also generates “developer partial class” files for each entity in the Domain Model.
These files are named “<EntityName>.cs” and are generated into the Domain Model project.
44 | P a g e
IdeaBlade DevForce Hello DevForce
45 | P a g e
IdeaBlade DevForce Hello DevForce
To that we‟ll add an empty project into which we‟ll subsequently put a newly created Entity Data Model. If
you know you‟ll be starting with an existing solution that already includes one or more Entity Models, you
can skip ahead to the section, “Build Your Domain Model Using the DevForce Object Mapper”.
2. Add a new Class Library project to your blank solution. Name this project “ServerModelNorthwindIB”.
We‟re naming it this because it will house an Entity Data Model, which would only be deployed server-
side in an n-tier deployment; and because that Entity Data Model will be based upon the NorthwindIB
database.
3. Delete the Class1.cs file that gets created by default in the new project. You will not use it.
4. Add a New Item, an ADO.NET Entity Data Model, to the project using the Entity Data Model wizard.
Name your model ServerModelNorthwindIB.edmx.
b. On the Choose Your Data Connection dialog, create or select the NorthwindIB data connection.
Rename the connection settings key name for the App.Config file to
“ServerModelNorthwindIBContext”. (That name will end up being used for the .NET
ObjectContext that will be generated by the Entity Framework.)
46 | P a g e
IdeaBlade DevForce Hello DevForce
47 | P a g e
IdeaBlade DevForce Hello DevForce
5. Visual Studio will create an Entity Data Model using the settings you specified, and will open it in its
graphical editor. Save the file without making any changes, then inspect it in the graphical editor.
Note the associations (“relationships” in database parlance) among the various entities. Order, for example,
has associations to the Customer, Employee, and OrderDetail entities. It‟s on the “many” end of 1-to-many
associations with Customer and Employee; it‟s on the “1” end of a similar association with OrderDetails.
Notice the corresponding Navigation Properties that were generated by the Entity Data Model wizard:
Customer, Employee, and OrderDetails.
48 | P a g e
IdeaBlade DevForce Hello DevForce
You could, at this point, do considerable further work on your Entity Data Model. For example, you might:
rename entities, entity properties, and entity sets, to fix pluralization, or for other reasons;
create new entities;
49 | P a g e
IdeaBlade DevForce Hello DevForce
1. From the Visual Studio main menu, select Tools / DevForce Object Mapper. DevForce, finding no domain
model already in the solution, displays a node named (New Model) in the navigational tree control.
2. Select the (New Model) node in the tree control. Observe the Project Settings:
DevForce has picked a project as the target for the generated domain model, but let‟s say we want to put the
generated DevForce class files into their own project. Click the <New project…> button. The following dialog
displays:
50 | P a g e
IdeaBlade DevForce Hello DevForce
We‟ll accept the defaults here, letting the Object Mapper create a new Windows Class Library project named
“DomainModel”. A directory of the same name will be created at the Location shown to house the generated
files.
Note also the checkbox labelled “Create Silverlight Domain Model project”:
This checkbox will only appear if you have a DevForce Universal or DevForce Silverlight license (but not a
DevForce WinClient license). In Universal it will default to being unchecked, as shown. In DevForce
Silverlight, it will default to being checked. For now we‟ll leave it as is, directing the Object Mapper not to
generate any Silverlight files.
When the Object Mapper saves the domain model and generates code, it will use the Namespace
(“DomainModel”) shown for the generated code, and will also name the EntityManager container
(“DomainModelEntityManager”) as shown. You‟ll use the EntityManager for retrieving data into your local
cache, for saving changes, and for many other data persistence operations.
51 | P a g e
IdeaBlade DevForce Hello DevForce
By default, the Object Mapper will, when you first save your work, do all of the following:
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.
3
If the developer has elected to have the .SSDL, .CSDL, and.MSL files generated by Visual Studio from the Entity Data
Model stored as loose files rather than embedded resources, those will also get copied to the executables folder.
52 | P a g e
IdeaBlade DevForce Hello DevForce
DevForce will find the Entity Data Model in your solution and suggest that as the model to add:
DevForce will display a node for the ServerModelNorthwindIB Entity Data Model as a child of the (New
Model) domain model.
7. Select the ServerModelNorthwindIB.edmx node in the tree control. Observe the settings in the details pane.
53 | P a g e
IdeaBlade DevForce Hello DevForce
The meanings and uses of these settings are described in detail in the “Object Mapping” topic document in the
DevForce Learning Resources. Please refer to that for details. For our purposes here, just accept all the default
settings.
8. We‟re going to do some further work on our model, but before we do, let‟s save our work. Do so by clicking the
“Save Domain Model” button on the toolbar (with a diskette icon), or by clicking the Save option on the File
menu, or by clicking the Save option on the shortcut menu associated with the (New Model) node.
A new project named for your DomainModel is created in the current solution. In that project your domain
model is generated. Both the project file and the domain model files have been placed in a directory to house
them.
The Location requested is for the project directory. By default that directory will be created immediately within
the directory where the existing solution resides.
In addition to the above, the Object Mapper created a
Solution Folder and moved the Domain Model project
into it. You may, if you like, choose to move the
project containing the Entity Data Model into that
folder as well; but that‟s up to you.
The Object Mapper has also generated a “designer”
code file,
DomainModel.ServerModelNorthwindIB.Designer.cs,
and placed it subordinate to DomainModel.ibedmx, the
XML file representing the domain model.
DomainModel.ServerModelNorthwindIB.Designer.cs
plays a role relative to the domain model similar to that
played by the ServerModelNorthwindIB.Designer.cs
file in relation to the entity data model,
ServerModelNorthwindIB.edmx. That is, it contains the
generated C# or VB code that represents the blueprint
for the runtime object model.
If you closed the Object Mapper, re-open it. Check the “Create developer classes” checkbox and then save the
domain model again.
54 | P a g e
IdeaBlade DevForce Hello DevForce
The Object Mapper also generated into the DomainModel project an app.config file that contains (among other
things) connection information for the data sources for all Entity Models contained in the Domain Model. In an
n-tier app, this connection information will not be deployed to the client machine, but only to the machine with
the DevForce Business Object Server (BOS).
Examining and Editing the Contents of the Entity Data Model in the Object Mapper
9. Reopen the Object Mapper if it is closed, and double-click the ServerModelNorthwindIB.edmx node in the tree
control, to expand it. You should see (hierarchically just below it) the container node for the
ServerModelNorthwindIB Entity Data Model, called ServerModelNorthwindIBContext. Note that this container
has the name that you specified, while creating the Entity Data Model in the EDM wizard, for saving the
connection information.
10. In the upper part of the details pane you should see the names of each type that you selected for inclusion in
ServerModelNorthwindIB. Note that both the type names and the corresponding Entity Set Names are the same
as the names of the tables on which they were based. But really, we‟d like the type names to be singular and the
Entity Set Names to be plural (e.g., the Customer type lives in the Customers entity set). We‟ll address that
presently.
11. In the lower part of the details pane see the property details for the type selected in the upper part. Select the
Order type in the upper partition of the details pane. Scroll through and note the many pieces of information
available about the Order object‟s properties that are visible or modifiable on the Simple Properties tab.
12. Select the Associations tab and note the associations discovered in the Entity Data Model. (These, in turn, were
created there because of the discovery of foreign key relations in the NorthwindIB database.)
55 | P a g e
IdeaBlade DevForce Hello DevForce
We see associations between Order and Customer, Order and Employee, and Order-OrderDetails.
13. Select the Navigation Properties tab. These properties were discovered in the Entity Data Model, where they
were generated as a consequence of the discovered relationships among the database tables. The Entity Data
Model designer named the navigation properties according to the name of their source table. But again, we
might well be a bit uncomfortable with the generated names, and wish that navigation properties that return a
collection had plural names, and ones that return a single object had singular names.
14. We‟ve already noted the navigation properties on the Employee entity (which arose from the Employee table‟s
relationship to itself), and their confusing names. We could rename those properties now, but first let‟s address
the pluralization issues in the names in the model in a global way.
15. In the tree control pane, select the ServerModelNorthwindIB.edmx node. Click the <Name Pluralizer> button in
the Entity Model Settings section. That launches the following model dialog:
16. Examine the default settings. By default, this tool will make Entity Set and Collection Navigation Property
names plural, but will make Entity Type names and Scalar Navigation Property names singular, regardless
of what they are now. Accept the default settings and click OK.
56 | P a g e
IdeaBlade DevForce Hello DevForce
17. Select the ServerModelNorthwindIBContext node in the tree control again and observe that the Entity Set
Names are now plural. Look at the Navigation Properties for the Order type and observe that the property
for OrderDetails is now named in the plural, whereas the others, which do not return collections, have been
left singular.
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:
DomainModel.ServerModelNorthwindIB.Designer.cs
gets overwritten;
57 | P a g e
IdeaBlade DevForce Hello DevForce
b. The individual files containing the partial “developer” classes are left alone; and,
c. Depending upon the nature of your changes, either or both of the DomainModel.ibedmx and
app.config files may be updated.
Our ServerModelAw2000 model looks something like the following when viewed in the Visual Studio Entity
Model Designer:
2. Note that the Employee entity for Adventureworks has an association to itself, just as the Employee entity
in NorthwindIB did. So it naturally has the same naming issues that we saw in that model for the navigation
properties that result from the recursive relationship. We‟ll fix them in the same way!
4
Note that the selection of Adventureworks2000 as the data source for our second Entity Data Model example was driven
much more by its likely familiarity and availability to you, the reader, than by any actual relevance it has as an extension to
NorthwindIB in a practical domain model. A more likely real-world scenario would be (as an example) one in which product
inventory information resides in one database and accounting information in another, with both types of information being
required by a target application.
58 | P a g e
IdeaBlade DevForce Hello DevForce
3. Select any other entity in the diagram and inspect its properties. Each Entity Set Name has been defaulted
to the same name as that used for the entity (e.g., the Entity Set for the Address type is named “Address”).
Don‟t do anything about the names at this time: we can address them more easily in the DevForce Object
Mapper (as we‟ve already seen).
Close the Entity Data Model based on the AdventureWorks2000 database.
4. From the Visual Studio menu, select Tools / DevForce Object Mapper. The Object Mapper will launch
with the existing domain model loaded.
5. Right-click the DomainModel.ibedmx node and select “Add Entity Model” from the shortcut menu. In the
File Open dialog, navigate to the ServerModelAw2000.edmx entity model, as necessary. When you open it,
you‟ll see the following message:
59 | P a g e
IdeaBlade DevForce Hello DevForce
This message results from the fact that the new Entity Model contains a type, “Employee”, whose name is
already being used in the domain model. (Remember, the ServerModelNorthwindIB model also contains
an Employee type.)
Respond by clicking the <Yes> button to allow the Object Mapper to rename the incoming class to resolve the
conflict.
6. Find and select the ServerModelAw2000Context node in the Object Mapper and inspect the imported
model. Note the following:
a. The Employee from AdventureWorks was renamed to “Employee1” to resolve the conflict with the
Employee from NorthwindEF.
b. All Entity Set Names are singular, as currently defined in the Entity Model (.edmx) file.
7. Double-click the Employee1 type in the upper right pane of the Object Mapper (titled
“ServerModelAw2000Context”) and rename it to “EmployeeAw2000”.
8. Note that all of these names are maintained in the Entity Model (.edmx) file, so changes will be written to
that file, and will be recognized by the Visual Studio Entity Data Model Designer.
9. In the tree control, re-select the “ServerModelAw2000.edmx” node. In the Entity Model Settings pane,
click the <Name Pluralizer> button. You‟ll see a dialog like the one you saw previously:
And again, if you accept all of the default, the Object Mapper will change
60 | P a g e
IdeaBlade DevForce Hello DevForce
Navigation properties that return a collection of objects so that they are plural.
Click <OK> to accept the defaults and allow the Object Mapper to fix up names in your model.
10. Perform the same fixup on the navigation properties for the EmployeeAw2000 entity that you did for the
Employee entity in the NorthwindIB model. Rename the “Employee1s” property to “DirectReports”;
rename the “Employee2” property to “Manager”.
11. Again select the ServerModelAw2000Context node in the tree control. Note the new Entity Set Names. All
are plural and look good except the name for the EmployeeAw2000 type. It was left unchanged because the
Object Mapper noticed that the type name and EntitySet name were different to begin with. Double-click
the Entity Set Name “Employees1” and change it to “EmployeesAw2000”.
12. Select File / Save from the Object Mapper menu. The Object Mapper generates a second “designer” class
file under the DomainModel.ibedmx file for the new AdventureWorks2000 entities.
Since the “Create developer classes” checkbox is still checked, it also generates developer partial classes
for the new entities: Address, CountryRegion, Department, EmployeeAw2000, and StateProvince. All
entities from both data sources are now part of the same domain model.
61 | P a g e
IdeaBlade DevForce Hello DevForce
62 | P a g e
IdeaBlade DevForce Hello DevForce
1. View the code in Employee.cs (or .vb), and find the Suggested Customizations region. Just before the
#endregion statement (#End Region in VB), add the following code:
C# /// <summary>
/// Age as of today
/// </summary>
public int Age {
get {
if (null == this.BirthDate) return 0;
DateTime oBirthDate = (DateTime)this.BirthDate;
DateTime oToday = DateTime.Today;
int oAge = oToday.Year - oBirthDate.Year;
if (oBirthDate.AddYears(oAge) > oToday) oAge--;
if (oAge < 0) return 0;
else return oAge;
}
}
VB ''' <summary>
''' Age as of today
''' </summary>
Public ReadOnly Property Age() As Integer
Get
If Nothing Is Me.BirthDate Then
Return 0
End If
Dim oBirthDate As Date = CDate(Me.BirthDate)
Dim oToday As Date = Date.Today
Dim oAge As Integer = oToday.Year - oBirthDate.Year
If oBirthDate.AddYears(oAge) > oToday Then
oAge -= 1
End If
If oAge < 0 Then
Return 0
Else
Return oAge
End If
End Get
End Property
This code defines a calculated property, Age, which returns the Employee‟s current age by calculating it from their
birthdate.
C# /// <summary>
/// Total revenue for this Employee's orders
/// </summary>
public double TotalOrderRevenue {
get {
double revenue = 0;
foreach (Order aOrder in this.Orders) {
foreach (OrderDetail aOrderDetail in aOrder.OrderDetails) {
revenue += aOrderDetail.Quantity *
Convert.ToDouble(aOrderDetail.UnitPrice) * aOrderDetail.Discount;
63 | P a g e
IdeaBlade DevForce Hello DevForce
}
}
return revenue;
}
}
VB ''' <summary>
''' Total revenue for this Employee's orders
''' </summary>
Public ReadOnly Property TotalOrderRevenue() As Double
Get
Dim revenue As Double = 0
For Each aOrder As Order In Me.Orders
For Each aOrderDetail As OrderDetail In aOrder.OrderDetails
revenue += aOrderDetail.Quantity * Convert.ToDouble(aOrderDetail.UnitPrice)
* aOrderDetail.Discount
Next aOrderDetail
Next aOrder
Return revenue
End Get
End Property
This property uses navigation properties on the Employee and Order entities, automatically generated by the Entity
Framework and carried through into the DevForce entities, to roll up the revenue from each line item of each order
written by the Employee.
3. Let‟s add one more bit of custom logic to see how we can modify the behavior of even those properties that
map directly to a back-end datasource and were therefore written into the generated class in
DomainModel.ServerModelNorthwindIB.Designer.cs. We‟ll make a simple adjustment: we‟ll convert the
Employee‟s LastName value to upper-case for display purposes while allowing entered or changed values to
retain whatever capitalization the end user entered. In other words, we want a value stored as “Davolio” in the
back-end datasource to be returned as “DAVOLIO” when we ask for it from the Employee object.
First let‟s have a look at that generated code. There doesn‟t appear to be much there, but it has an amazing
amount of flexibility built into it, behind the scenes:
64 | P a g e
IdeaBlade DevForce Hello DevForce
An important part of this flexibility is provided by DevForce‟s support for methods known as property
interceptors. Let‟s implement one now.
4. Enter the following code below the TotalRevenueCost property that you most recently added:
C# [AfterGet(EntityPropertyNames.LastName)]
public void UppercaseLastName(PropertyInterceptorArgs<Employee, String> args) {
var lastName = args.Value;
if ( !String.IsNullOrEmpty(lastName)) {
args.Value = args.Value.ToUpper();
}
}
VB <AfterGet(EntityPropertyNames.LastName)> _
Public Sub UppercaseLastName(ByVal args As PropertyInterceptorArgs(Of Employee, String))
Dim lastName = args.Value
If Not String.IsNullOrEmpty(lastName) Then
args.Value = args.Value.ToUpper()
End If
End Sub
UppercaseLastName() simply converts the current value of the LastName property (passed to the method in
args.Value) as desired, and the work is done.
The [AfterGet] attribute with which the public method UppercaseLastName() is decorated tells DevForce to call that
method during any get operation for the designated property, just after the raw value is retrieved from the local
instance of the object. The static5 EntityPropertyNames.LastName property, included in the attribute, simply returns
the string-valued name of the LastName property. (The EntityPropertyNames class was automatically created by
DevForce during Object Mapper code generation so you don‟t have to hard-code property names and risk
misspelling them, in this and other contexts.)
You can call the method anything you like (since the [AfterGet] attribute defines its role), but the signature does
requires the args parameter, which is an instance of IPropertyInterceptorArgs. The version used here employs the
generic version of IPropertyInterceptor, which fully specifies the type of both the property and its containing entity,
5
“Shared” in VB
65 | P a g e
IdeaBlade DevForce Hello DevForce
so that you need not cast Args.Value within the method code. The compiler already knows (in this example) that it‟s
a string.
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();
}
66 | P a g e
IdeaBlade DevForce Hello DevForce
VB Imports IdeaBlade.EntityModel
Imports DomainModel
Module Module1
Sub Main()
GetEmployees()
End Sub
End Module
5. Make Console01 the Startup Project for your DevForce01 solution. Compile and then run the app. You should
see output similar to the following:
67 | P a g e
IdeaBlade DevForce Hello DevForce
Do you have Team Test? Look for “Test” among the Visual Studio menus. If it‟s there, you‟ve got
Team Test; if it isn‟t, you don‟t. For now you should skip ahead.
Before you do, think about how you will test your application. If you can‟t afford Team Test, you
might consider NUnit. It‟s solid and it‟s free. While it won‟t generate the template tests (we‟ll see
Team Test do that shortly), it is otherwise remarkably similar to testing with Team Test.
It‟s easy to get started with the new Visual Studio Team Test.
1. Open Employee.cs (or .vb) in code view, and right-click in the Code View window.
2. Pick “Create Unit Tests …” The “Create Unit Tests” Dialog appears.
68 | P a g e
IdeaBlade DevForce Hello DevForce
5. For “Output Project”, accept “Create a new Visual C# [or VB] Test Project”...
6. Click [OK]
7. Enter “DomainModel.Test” as the project name, when asked, and click <Create>.
After some grinding in the background, you will see the DomainModel.Test project added to the solution, with
references to the DomainModel and the necessary IdeaBlade assemblies. You will also see an EmployeeTest.cs (or
.vb) tab and an AuthoringTests.txt tab. AuthoringTests is a non-functional introduction to testing. It‟s well worth
reading at some point, but for now, just close it.
69 | P a g e
IdeaBlade DevForce Hello DevForce
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
a. On the Visual Studio Test menu, select Edit Test Run Configurations and then Local Test Run
(localtestrun.testrunconfig).
c. Click <Add File...>. Change the Objects of type select to “All files(*.*)”, and find the app.config
file in the folder for the solution‟s executable project (DevForce01\Console01), select it, and click
the <Open> button. The localtestrun.testrunconfig dialog should then appear as follows:
70 | P a g e
IdeaBlade DevForce Hello DevForce
Note that there is no need to set the ConfigFileLocation in code (e.g., in one of the test initializer methods),
because, as in any DevForce app, DevForce automatically searches the main application folder
(BaseAppDirectory) for an app.config file. It will therefore find the one deployed in the test deployment folder
(TestDeploymentDir).6
4. Next we need to ensure that the server model assembly will be deployed to the test result directory. Click <Add
File...> again. Navigate to the bin\debug folder under the project folder for your Entity Data Model project (the
ServerModelNorthwindIB folder, in our case). Select the ServerModelNorthwindIB assembly:
6
You can find detailed information about how DevForce finds configuration in the appendix to this chapter entitled “Probing
Sequence for the App.Config File”.
71 | P a g e
IdeaBlade DevForce Hello DevForce
5. If you have elected to have the Entity Data Model artifacts stored as loose files rather than to be embedded in
the server model assembly (as is the default), then you will also need to add the three components of the
ServerModelNorthwindIB model (the .ssdl, .csdl, and .msl files). You will find these in the same folder where
you found the model assembly.
6. On the localtestrun.testrunconfig dialog, click <Close> and respond Yes when prompted whether to save
changes to the testrunconfig.
C# /// <summary>
///A test for Employee Constructor
///</summary>
[TestMethod()]
public void EmployeeConstructorTest() {
Employee target = new Employee();
string desiredTypeName = "Employee";
string actualTypeName = target.GetType().Name;
Assert.IsTrue(actualTypeName == desiredTypeName,
"Created type, '" + actualTypeName + "', is incorrect. It should be '" +
desiredTypeName + "'.");
}
VB ''' <summary>
72 | P a g e
IdeaBlade DevForce Hello DevForce
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.
73 | P a g e
IdeaBlade DevForce Hello DevForce
Our example uses the NorthwindIB database as its data source. It has well-known data and we‟re not going to make
any changes just yet, so we‟re fine.
1. We‟re not going to be purists now – this is just a tutorial – so we‟ll be sloppy with our first test. Let‟s rewrite it
so it looks like this:
C# /// <summary>
///A test for Age
///</summary>
[TestMethod()]
public void AgeTest() {
DomainModelEntityManager manager =
DomainModelEntityManager.DefaultManager;
Employee target = manager.Employees
.Where(e => e.LastName.ToLower() ==
"Davolio".ToLower()).ToList().First<Employee>();
int actual = target.Age;
int approxAge = System.DateTime.Now.Year - target.BirthDate.Value.Year;
Assert.IsTrue(approxAge - actual <= 1);}
VB ''' <summary>
'''A test for Age
'''</summary>
<TestMethod()> _
Public Sub AgeTest()
Dim manager As DomainModelEntityManager = DomainModelEntityManager.DefaultManager
Dim target As Employee = manager.Employees _
.Where(Function(e) e.LastName.ToLower() = "Davolio".ToLower()).ToList().First()
Dim actual As Integer = target.Age
Dim approxAge As Integer = Date.Now.Year - target.BirthDate.Value.Year
Assert.IsTrue(approxAge - actual <= 1)
End Sub
A “Test Results” window appears at the bottom of Visual Studio. The test passes!
74 | P a g e
IdeaBlade DevForce Hello DevForce
Just FYI, in case you‟re new to Team Test: you can also run the tests in debug mode so you can insert breakpoints.
Instead of starting the test with the <Run Selection> button, to run in debug mode, right-click the test and select
Debug Selection.
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.
1. In the Solution Explorer, right-click the solution node and select a new Windows Forms Application. Name
the project “WinForms01”.
2. Set a reference in the WinForms01 project to the DomainModel project so our form can see the entities in
the domain model. Set references to IdeaBlade.Core and IdeaBlade.EntityModel and so you can use the
DevForce types you‟ll need. Finally, set a reference to the .NET assembly WindowsBase, as it contains an
interface used by the DevForce BindableList<T> type that you‟ll use in data binding.
75 | P a g e
IdeaBlade DevForce Hello DevForce
3. In the designer, select Form1. In the Properties window, change the Text property of the Form from
“Form1” to “Employees”. In the Solution Explorer, rename the file containing the form from “Form1.cs”
(or .vb) to “EmployeeForm.cs” (or .vb). Say yes when asked whether you want to rename references to the
form in the project:
4. Find the BindingSource control in the Toolbox (“All Windows Forms” group) and drag two of them on to
the form. Name one of them “_ordersBindingSource” and the other “_employeesBindingSource”. (We‟re
using the underscore prefix “_” for elements that will be scoped at the class level in our form, as these
will.)
5. Drag a BindingNavigator from the ToolBox to the form, positioning it along the top edge. Name that
“_employeesBindingNavigator”.
6. Find the “IdeaBlade DevForce” group in the Toolbox. (Remember, it won‟t be there unless you installed
the DevForce WinForms UI support when you installed DevForce!) Drag a ControlBindingManager and
then a DataGridViewBindingManager on to the form. Rename the ControlBindingManager
“_employeesControlBindingManager”; rename the DataGridViewBindingManager
“_ordersDataGridViewBindingManager”.
7. In the component tray, select the _employeesControlBindingManager. Then, in the Properties window,
assign the _employeesBindingSource to the BindingSource property for the
_employeesControlBindingManager.
76 | P a g e
IdeaBlade DevForce Hello DevForce
9. In the component tray, select the _employeesControlBindingManager. Next, right-click the smart tag that
shows up at its upper right corner, and choose Autopopulate Controls. In the dialog “Bind to which object
type?”, select the DomainModel assembly and the Employee type, then click <Ok>. You‟ll see the
DevForce “Configure Databindings” designer:
10. From the Properties tree control, drag the following properties onto the Autopopulation tab: LastName,
FirstName, BirthDate, Age, and Photo.
11. Click the Naming Conventions button and add an underscore in front of the default text of “{0}{1}” to
make it “_{0}{1}”. Click the <Update Sample> button if you want to see what it will look like. Then click
<OK>.
77 | P a g e
IdeaBlade DevForce Hello DevForce
12. On the “Configure DataBindings” dialog, click <OK> to close it and autopopulate the form with controls
for your selected properties. Rearrange the controls as desired. We‟ll move the PictureBox for the Photo to
the right of the other controls, and delete its label.
13. Now select the _ordersDataGridViewBindingManager, click the Smart Tag, select “Configure
Databindings”, select DomainModel as the assembly containing your target types, and select Order as the
target type. Into the grid, drag the properties OrderDate, RequiredDate, ShippedDate, and FreightCost.
14. Expand the Customer property in the tree control and drag the CompanyName property of the Customer
onto the grid. Drag it upward to make it the top row. Edit the value in the “Column Title” column from
“Customer Company Name” to “Company”.
15. Click <OK> to close the dialog. Whoops! The designer warns you that you haven‟t linked your selected
properties to a grid (see picture below). Response that <No>, you don‟t want to exit just yet.
16. Click the <Create Grid> button, and name the grid to be created “_ordersDataGridView”. Click <OK>,
then click <OK> on the main dialog again. (If you get prompted asking whether to enlarge the form to
accommodate the new control, say yes.) The designer configures a DataGridView control and plops it on to
your form. Position and size it as desired. Your form should now look something like the following:
78 | P a g e
IdeaBlade DevForce Hello DevForce
17. There‟s more configuration we could do in the designer, but we‟ll choose to do it in the “code behind” for
our form, instead. Make that look as follows:
C# using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using IdeaBlade.EntityModel;
using IdeaBlade.Util;
using DomainModel;
namespace WinForms01 {
public partial class EmployeeForm : Form {
public EmployeeForm() {
InitializeComponent();
this.Load+=new EventHandler(EmployeeForm_Load);
}
79 | P a g e
IdeaBlade DevForce Hello DevForce
_ordersBindingSource.DataSource = _orders;
}
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
80 | P a g e
IdeaBlade DevForce Hello DevForce
LoadData()
End Sub
End Class
Run It!
19. Build and run your app.
81 | P a g e
IdeaBlade DevForce Hello DevForce
Please note: The code solutions that accompany this document in the Learning Resources reflect the work
to this point, with the WinForm user interface.
Add a WPF UI
Please see the Learning Units that install with DevForce for examples of a DevForce app with a WPF user interface.
Features described in the section are included with the DevForce Silverlight product.
Re-open the Object Mapper and check the “Generate Silverlight Projects” checkbox:
82 | P a g e
IdeaBlade DevForce Hello DevForce
Doing so causes a ComboBox for the project name to appear, and a button to create a new project. Click the <New
Project…> button to see the Create Project dialog:
83 | P a g e
IdeaBlade DevForce Hello DevForce
We‟ll accept the default name for the project, “DomainModelSL”, by clicking <OK>. Then we will again save our
Domain Model.
After closing the Object Mapper, we see that it has created a new project named “DomainModelSL”.
Now that we have our Silverlight copy of the DomainModel, it‟s time to add the executable project. .NET provides
a Silverlight Application project template, but that‟s not what you want:
84 | P a g e
IdeaBlade DevForce Hello DevForce
Why not? Because we‟ve provided a DevForce-aware template that will get you there a lot faster, and with much
less trouble.
Under the Visual C# (or VB) project types, find the DevForce section and select the “DevForce Silverlight
Application” template. We‟ll name our project “DevForceSilverlight01”:
Visual Studio creates a Silverlight project of the specified name. It also creates a web project to host the Silverlight
app, naming it “<appname>Web”, or in our example, “DevForceSilverlight01Web”.
85 | P a g e
IdeaBlade DevForce Hello DevForce
86 | P a g e
IdeaBlade DevForce Hello DevForce
assembly, then that assembly and the assembly holding the linked SL model must have the same name and
namespace.
If it‟s running from the file system, the URL in the address bar will look like a file path (and you‟ll
see no evidence of retrieved data where you otherwise would have).
87 | P a g e
IdeaBlade DevForce Hello DevForce
88 | P a g e
IdeaBlade DevForce Hello DevForce
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.
89 | P a g e
IdeaBlade DevForce Hello DevForce
The app.config in the DomainModel project (#2) typically gets there by being generated by DevForce. It contains,
most importantly, an ideaBlade.configuration section with edmKeys that include connection information and other
settings related to specific data sources; and other settings that control or reflect such things as application logging
behavior, location of the Business Object Server, etc. The connection information included in the edmKeys usually
originates from the EDM project‟s app.config (#1), being copied from there by DevForce. However, the developer
can add to it manually or using the DevForce Configuration Editor. 7
The app.config file associated with the Entity Data Model enables the Entity Data Model designer to find the EDM‟s
datasource. As such, it is essential to the design-time utility of that designer. The app.config file associated with the
Domain Model is updated at design time, but other than to be available for update, its contents have no design-time
function. The same is true for the app.config associated with the executable project. The latter, however, becomes
the (only) copy of app.config used at runtime. It is actually copied and renamed by Visual Studio to reflect the name
of the assembly; for example, if the UI project above is used to create an assembly named UI.exe, then Visual Studio
will create a copy of the app.config file in that UI project and name the copy UI.exe.config. The latter is the version
used at runtime by the .NET framework.
7
“Config Editor” under IdeaBlade DevForce / Tools on the Windows Start menu.
90 | P a g e
IdeaBlade DevForce Hello DevForce
2. The DomainModel app.config (#2) is updated using information from one or more EDM app.configs. This
update occurs either upon a Save in the DevForce Object Mapper, or when the developer responds “Yes” to
the following prompt, which is presented after the developer saves a change to the Entity Data Model8:
3. The app.config associated with the executable project (#3) is updated using information from the
DomainModel app.config. This occurs at build time 9 when DevForce discovers a mismatch between the
information in the ideaBlade.configuration section of the executable project app.config and the
corresponding information in the DomainModel copy (#2). Before updating app.config #3 DevForce
prompts you as follows:
Note that, except for updating the ideaBlade.configuration and configSections elements of the executable project‟s
app.config (#3), DevForce leaves the app.config alone. Thus, it can contain any other elements and information
needed by the app; those will be left undisturbed.
8
DevForce watches the Visual Studio IDE for such a change, and responds when it occurs by presenting this prompt.
9
Specifically, when the executable project is built. This is performed by a Build Event handler attached by DevForce to the
executable project.
91 | P a g e
IdeaBlade DevForce Hello DevForce
Monitoring Activity
What is actually happening as we run the applications? When is it asking for data? What does the SQL look like?
SQL Profiler
We can always monitor activity on the SQL Server using SQL Profiler. Here we assume SQL Server 2005.
Launch the SQL Server Management Console.
Select “Tools ►SQL Server Profiler” from the menu.
Select “File ►New Trace ..” from the Profiler menu and connect to your database server
Click [Run] on the [Trace Properties] dialog.
Return to Visual Studio and re-run the application [F5]
92 | P a g e
IdeaBlade DevForce Hello DevForce
The trace window fills, showing us exactly how we’re hitting the database.
DevForce DebugLog
We may want to supplement the SQL Profiler with a DevForce tool that helps us see both the database activity and
the other application activity that surrounds it.
DevForce applications generate a trace log10 every time you run the application. The log appears in the executable
directory; its default name is “DebugLog.xml”11.
Open Windows Explorer.
Navigate to the ..\bin\debug directory under the UI directory.
Launch DebugLog.xml.
Each row speaks of some event during the life of the last application run. You‟ll see database access events among
other event occurring from the start of the application until it shuts down.
You can launch the DebugLog while the application is running and refresh the browser from time to time to see how
the log is progressing as you move through the application.
DevForce TraceViewer
The DevForce TraceViewer affords a friendlier and more dynamic look at logged activity. You can launch the
TraceViewer from the IdeaBlade DevForce/Tools menu. It can also be linked directly into your application. See the
Object Persistence chapter for details.
10
You can turn it off or filter it.
11
There are companion .css and .xslt files in that directory as well so that the log displays in the browser nicely. You can
rename the log in the App.Config file.
93 | P a g e
IdeaBlade DevForce Hello DevForce
94 | P a g e
IdeaBlade DevForce Hello DevForce
<connectionStrings>
...snip
</connectionStrings>
<configSections>
...snip
</configSections>
<ideablade.configuration version="5.00" updateFromDomainModelConfig="Ask">
…snip
</ideablade.configuration>
</configuration>
1. If the ConfigFileLocation property of the IdeaBladeConfig object has been set in the executing code,
DevForce will search the indicated location for a file named or matching “*.exe.config”, “web.config” or
“app.config”, in that order. Note that files matching “*.dll.config” will not be found.
2. If the ConfigFileLocation property was not set, or a suitable config file was not found in the indicated
location, then DevForce will look to see if the IdeaBladeConfig.ConfigFileAssembly property has been set.
If so, it will look for an embedded resource named “app.config”.
3. Next, the BaseAppDirectory is searched for a file named or matching “*.exe.config”, “web.config” or
“app.config”, again in that order. This property is read-only and determined at run time, and is usually the
directory from which the entry assembly was loaded. However, in a test project executable, which has no
entry assembly, the AppDomain.BaseDirectory is used. In MSTest, this will be the
TestContext.TestDeploymentDir.
4. DevForce next searches for an embedded resource named “app.config” in the entry assembly. This is
ignored in the case of a test project because the entry assembly is null.
Finally, DevForce will look for an embedded resource named “app.config” in an assembly named “AppHelper”.
This search is done for reason of backward compatibilility with DevForce applications that use the AppHelper
project formerly generated by the DevForce Object Mapper.
95 | P a g e
IdeaBlade DevForce Class Libraries
Class Libraries
Class Libraries
Important Namespaces
The IdeaBlade.EntityModel.Entity
Finding Help on DevForce
XML Documentation
IntelliSense
The Object Browser
Class View
Class Diagram
You do not have to put DevForce DLLs in the GAC on your application client machines. In most cases
your installation will likely be via XCOPY or ClickOnce.
You should turn to the Reference Help for the minute details about the DevForce classes, interfaces, methods,
properties and events. That effort can be like looking through a keyhole. Here you can learn a bit about the room
you‟re seeing through that keyhole.
While an assembly can host multiple namespaces and a DLL can host multiple assemblies, each DevForce
namespace is almost invariably in its own assembly and each assembly is in its own DLL. Thus, as our tour
proceeds namespace-by-namespace, we are also walking from one assembly and DLL to the next.
In this chapter we‟ll also give you some ideas about how to use Visual Studio to get to this information quickly.
Important Namespaces
All DevForce namespaces have an “IdeaBlade” prefix. The discussion below elides that prefix for brevity so, when
we refer to “EntityModel”, we mean “IdeaBlade. EntityModel”.
See the DevForce Reference Help or Consolidated Help for much more detail on the content of these namespaces.
96 | P a g e
IdeaBlade DevForce Class Libraries
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.
C#
or aCustomer.EntityAspect
VB
Through the EntityAspect you have access to a rich set of (a) properties that define the entity‟s state, and (b)
persistence-related methods. These are listed below: method names terminate with parentheses.
See the DevForce reference help (IdeaBlade DevForce / Documentation / DevForce Help from the Windows Start
Menu) for more detail.
97 | P a g e
IdeaBlade DevForce Class Libraries
Member Function
AcceptChanges() Accepts all changes to this Entity, returning the EntityState to Unchanged.
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.
EntityMetadata The EntityMetadata for this Entity. The available EntityMetadata includes all of the
following properties:
Member Description
98 | P a g e
IdeaBlade DevForce Class Libraries
Public Methods
Name Description
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.
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.
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.
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.
99 | P a g e
IdeaBlade DevForce Class Libraries
SavingErrorMessage Gets the description of any error that occured during the most recent save of this
entity.
SetAdded() Forces this entity into the EntityState of Added. You will usually have no reason to
call this method from application code. The EntityState is automatically set to
Added by the framework when a new entity is added to an EntityManager.
SetModified() Forces this entity into the EntityState of Modified. You will usually have no reason
to call this method from application code. The EntityState is automatically set to
Modified by the framework when any EntityProperty of the entity is changed.
VerifierEngine Gets the IdeaBlade.Validation.VerifierEngine shared by all entities within the same
EntityManager.
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
XML Documentation
These techniques are only as good as the XML documentation embedded in the code.
IntelliSense
You probably know that if you hover the mouse over a class or method for a moment, you‟ll see a tool tip that gives
some brief syntactical information.
IntelliSense can do much more. A full discussion is out of scope but we thought it worth mentioning a few of the
tricks we use all of the time.
100 | P a g e
IdeaBlade DevForce Class Libraries
We find List Members and Parameter Info to be the most informative short-cuts.12
Press the accelerator key combination for List Members within an identifier and IntelliSense displays its
description straight from the <summary/> tag of its XML documentation. This works for a member, too.
Enter the dot („.‟) to the right of an identifier and again you get an open list. IntelliSense scrolls to the most
recently used option if there is one. Clicking on the option reveals its description.
Press the accelerator key combination for Parameter Info while your cursor is in the parameter list and
you‟ll see the method overloads and a description of the nearest parameter if the developer provided the
XML documentation
Use the up and down arrow to scroll among the overloads.
12
Word completion is a big help too although that‟s more about saving keystrokes than discovery. Type just a few letters of the
desired identifier, then signal work completion; VS fills in your choice.
101 | P a g e
IdeaBlade DevForce Class Libraries
There are three panes: a tree view of the assembly, list of member of the selected type, and the XML
documentation.
You can expand any node in the tree view to see its inheritance chain and examine any type within that chain:
Search
Best of all, you can search for a word (or part of a word) among your referenced assemblies and the .NET
framework assemblies.
102 | P a g e
IdeaBlade DevForce Class Libraries
In this example, we browse just among the solution assemblies and search for the word “manager”. After we click
the green search button, the result might look like this:
We can browse around in this filtered set as before. We click the “Clear Search” icon when we‟re done.
Class View
Class View is just like the Object View but narrows the scope to just the assemblies we can edit in our Solution. You
can open the Class View from the menu (View ► Class View).
Class Diagram
With both the Object Browser and Class View we can examine the assemblies but we cannot change their XML
documentation. We can only change the XML documentation of the code we‟re editing.
As with previous versions of Visual Studio we can enter the XML comments within the Code View.
Visual Studio 2005 introduced the Class Diagram which promises to be the means for programming visually. You
can open a class diagram in Visual Studio 2008 using the View Class Diagram button on the Class View toolbar.
The Class Diagram displays a lovely design service onto which we can drag our existing classes and mark
associations among them like so:
103 | P a g e
IdeaBlade DevForce Class Libraries
We can add classes and add members and the Class Diagram will stub in the code which we later flesh out by
switching back to Code View.
There are no round-tripping problems because the Class Diagram is just another view on the exact same code; there
is no intermediate format that must be translated as we shift from one perspective to the other.
This will be a wonderful documentation tool as soon as Microsoft smoothes some of the rough edges. You soon
discover that you‟re wrestling with positioning the blocks and the lines that connect them. Just as you have it about
right, you touch something and all of the graphics scramble to odd and inconvenient positions.
We are not enamored of the code generation either. It generates a nice stub but we can do better with code snippets
and, really, how hard is it to write a stub method or property? If you like it, by all means use it.
104 | P a g e
IdeaBlade DevForce Business Object Mapping
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.
The developer uses the ADO.NET Entity Data Model Designer, or XML hand-coding, or a likely
combination of the two, to develop one or more Entity Data Models that describe conceptual entities and
map them to a back-end storage schema.
The developer uses the DevForce Object Mapper to create a “Domain Model” that combines one or more
Entity Data Models into a single logical model. The resulting Domain Model encompasses entities from all
source Entity Models, uniting them in a single structure that permits relationships to be defined amongst
them and (at runtime) updates to be handled transactionally across them.
The developer uses the Object Mapper to enhance and adjust the Entity Models in a variety of ways. The
changes made to the source Entity Model(s) by the Object Mapper do not conflict with the demands of the
Entity Data Model Designer, so the developer can work with confidence on her business model using either
or both tools, throughout the development life cycle.
The Object Mapper then generates DevForce Entity class files from the Domain Model, just as the Entity
Data Model Designer generates class files from the Entity Model(s). These class files define the
specification for the runtime object model.
This chapter covers addresses the building of the Business Object Model. Another chapter, “Object Persistence”,
describes the use of that model in persistence operations such as queries, creates, updates, deletes and saves.
Introduction
The ADO.NET Entity Framework provides a mapping scheme for declaring how its persistence mechanisms should
translate between data in a data source and data in an in-memory business object state. The Entity Framework,
however, provides only for operation in client-server mode. Its object model includes detailed information about the
105 | P a g e
IdeaBlade DevForce Business Object Mapping
schema of the back-end data sources, and the Entity Framework depends upon locally available connection
information to carry out its persistence operations with those data sources.
The DevForce Composite Object Model, by contrast, is entirely free of knowledge or information about back-end
data sources, and so can be deployed to client machines without compromising the security of the persistent data
stores (which may be relational databases or web services). Furthermore, the DevForce persistence framework that
uses the Domain Model provides and manages a local cache in which data can be stored during application sessions,
permitting extensive and complex data maintenance work independent of the back-end data stores.
In short, DevForce extends the capabilities of the Entity Framework from client-server to n-tier; and in the process,
provides all the benefits of n-tier deployment. These include:
106 | P a g e
IdeaBlade DevForce Business Object Mapping
1. Begin with an existing Visual Studio solution containing at least one project with an ADO.NET Entity Data
Model (EDM) in it.
The solution shown below has an EDM based on the NorthwindIB database that ships with DevForce. This
is a modified version of the NorthwindEF database distributed by Microsoft. 13
The Object Mapper launches with no Entity Data Model loaded in this first-time use:
13
A discussion of the differences between NorthwindIB and NorthwindEF, and the reasons for them, is included as an Appendix
to this chapter.
107 | P a g e
IdeaBlade DevForce Business Object Mapping
3. Click on the Domain Model name – “(New Model)” -- in the tree control to select it. Now inspect the
properties of the Domain Model in the Detail pane.
108 | P a g e
IdeaBlade DevForce Business Object Mapping
Domain Model Project. This will display the path to the folder for the Domain Model project and files.
Use the <New project…> button to create a new Domain Model project; otherwise select an existing
project from the dropdown list.
Create Silverlight Domain Model Project checkbox. Check this box if you wish to create a Silverlight
project. (See the chapter, “DevForce Silverlight Apps” for more detail.) This option is only available in the
DevForce Silverlight and DevForce Universal products; not in DevForce WinClient.
Namespace. Code for the Domain Model will be generated into the namespace shown in the Settings area.
By default, the namespace will be “DomainModel”. You can change this if you prefer a different name for
the namespace.
Entity Manager Name. By default, the container for the Domain Model‟s collections of objects will be
“DomainModelEntityManager”. You can change this as well. You will be able to reference its collections
in LINQ and elsewhere as follows:
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:
.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.
109 | P a g e
IdeaBlade DevForce Business Object Mapping
110 | P a g e
IdeaBlade DevForce Business Object Mapping
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.
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.
VB
111 | P a g e
IdeaBlade DevForce Business Object Mapping
Injected Base Types. This button launches a dialog to help you define base classes to sit in the inheritance
hierarchy of your business type classes. This will be discussed below.
Name Pluralizer. This button launches a tool to help you save a great deal of time making the pluralization
of your type names orderly and sensible. This will be discussed below.
Verification Interceptors. These options give you control over which verification-related interceptors are
coded into your generated property definitions. You can choose among the options shown below:
Verification interceptors are discussed in Chapter 7, “Validation Through Verification,” of this Developer
Guide.
6. Click on the ServerModelNorthwindIbContext node in the navigational tree control. When you‟re
positioned in the tree on a container node such as this one, you have a comprehensive, quick reference view
of the entity types and their properties:
112 | P a g e
IdeaBlade DevForce Business Object Mapping
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:
This shows you the properties defined in the ServerModelNorthwindIB Entity Model for navigating from
the Order object to its related entities. The Order has a Customer who placed it, an Employee who wrote it,
a collection of line items (which are OrderDetail objects), and, potentially, a related InternationalOrder.
These properties were generated by the EDM Designer in response to its discovery of relationships in the
targeted database schema.
113 | P a g e
IdeaBlade DevForce Business Object Mapping
9. Select the Associations tab. You will see the background information about the associations defined in the
model (which led to the generation of the navigation properties you just examined).
A foreign key constraint, FK_Order_Customer, found in the database, relates the Order and Customer
tables in a many-to-1 relationship. (One customer can place many Orders.)
Another, FK_Order_Employee, relates the Order and Employee tables in a many-to-1 relationship. (One
Employee can write many Orders.)
A third, FK_OrderDetail_Order, relates the Order and OrderDetail tables in a 1-to-many. (One Order can
have many details – line items).
114 | P a g e
IdeaBlade DevForce Business Object Mapping
10. Return to the Simple Properties tab and examine the detail on the properties of the Order Entity:
115 | P a g e
IdeaBlade DevForce Business Object Mapping
Many of the property settings are self-explanatory, but a few need further explanation:
Concurrency Strategy
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).
116 | P a g e
IdeaBlade DevForce Business Object Mapping
The Verification Setter Mode controls what verification-specific interception points will occur for the generated
properties. Verification interceptors are in Chapter 7, “Validation Through Verification”.
Clicking <Yes> will save the domain model with a default name and location, those being:
Name. The domain model will be given the same name as the (first) Entity Data Model added; and
Location: The domain model will be saved into the same project as the Entity Data Model most recently
added.
If you wish to exert explicit control over where the domain model is saved and what it is named, click <Cancel>
when you see the above dialog, then select the domain model node in the navigation pane of the Object Mapper
dialog (which will have the title “(New Model)” if the domain model has not previously been saved):
Using the ComboBox labeled Domain Model Project, you can specify into which of your solution‟s projects the
domain model will be generated, or you can click the <New project…> button to create a new project just for the
domain model.
117 | P a g e
IdeaBlade DevForce Business Object Mapping
By default, a new project will be named “DomainModel”. Note that the domain model itself, encoded in a file with
extension .ibedmx, will always be named the same as the folder into which it is generated.
When you create the new project, a solution folder of the same name will be created, and both the domain model
project and all existing Entity Data Model projects that are part of the domain model will be moved into that
solution folder. The domain model itself will be generated and saved only when you so order.
The File menu contains options related to the Domain Model file as a whole.
The Model menu contains options related to operations on the Entity Model components of the domain model.
Many of these options are also available on right-click shortcut menus associated with nodes of the navigation tree.
118 | P a g e
IdeaBlade DevForce Business Object Mapping
The View menu contains a toggle option to suppress or display the navigation window (tree control).
The Help menu contains an About option from which you can learn the version number and license type associated
with the copy of DevForce you have installed.
119 | P a g e
IdeaBlade DevForce Business Object Mapping
This dialog permits you to introduce base types between levels in your inheritance hierarchy. The most common
operation will be to create a base entity that inherits from IdeaBlade.EntityModel.Entity, and to make it the base
type for all business object types in the model. This you would do as follows:
1. Click the <Add> button.
2. In the “Add Injected Base Type” window, type the desired name of your base entity, and accept the
inheritance default of “Entity”.
4. Now select the row created for BaseEntity and click <Set Default>.
120 | P a g e
IdeaBlade DevForce Business Object Mapping
BaseEntity becomes the default base class for all your business object types.
You can all other base types and assign any subset of your business object types to inherit from them. Note,
however, that you can insert at most one base type between each pair of concrete types. Thus, you can have Order
inherit from BaseEntity and International Order inherit from NonDomesticOrder; but you can‟t use the Injected
Types dialog to make Order inherit from OrderBase and OrderBase inherit from BaseEntity. If you need to do that,
do the following:
1. Define base types BaseEntity and OrderBase.
2. Make BaseEntity the default type.
3. After close the Injected Types dialog, set Order to inherit from OrderBase.
121 | P a g e
IdeaBlade DevForce Business Object Mapping
The Name Pluralizer can also be launched from the main menu via Edit / Name Pluralizer, or from the context menu
associated with the Entity Model node in the navigational tree control:
Launching the Name Pluralizer by any of these mechanisms presents you with the following modal dialog:
We‟ll get to the mechanics of this tool in a moment, but let‟s get a little background first.
The Entity Model Designer, by default, always names the Entity Sets and Entity the same as the table on which they
are based. In the NorthwindIB database, tables were named in the singular (“Customer” rather than “Customers”,
122 | P a g e
IdeaBlade DevForce Business Object Mapping
etc.), so both the Entity Types and the Entity Sets are named in the singular as well. We‟re going to want the Entity
Set names to be plural, as we find it more natural to refer to the “Customers” collection, which contains individual
“Customer” entities.
The EDM Designer also makes no attempt to address the number (singular or plural) of the navigation properties. It
simply names them the same thing as the corresponding Entity. You may want to do better than that. Most
developers want the navigation properties that return a collection to have plural names (“OrderDetails” instead of
“OrderDetail”), and the navigation properties that return a single object to have singular names (“Customer”,
“Employee”, “InternationalOrder”).
Now let‟s take a second look at the Name Pluralizer dialog, first shown above. The Name Pluralizer will fix our
pluralization problems with both Entity Set names and Navigation Property names at one stroke. You can change
them in either direction, but for most developers, the default settings will be perfect. Regardless of what their names
are now, Entity Sets and Navigation Properties that return a collection will end up with plural names; Entity Types
and Navigation Properties that return a single object with singular ones. (The <Reset Defaults> button will always
re-establish the settings you see here.)
You just click <OK> or <Apply> to perform the work. (The <Ok> and <Apply> buttons have the standard
Windows behaviors: both perform the indicated operations, but <Ok> will also close the dialog, whereas <Apply>
will leave it open.)
123 | P a g e
IdeaBlade DevForce Business Object Mapping
Note the pluralization of the Entity Set and Navigation Property names after applying the Pluralizer. This will save
you a lot of time, particularly if your model is large!
To add web service entities to your domain model, begin by selecting File / Add Web Service from the Object
Mapper menu:
124 | P a g e
IdeaBlade DevForce Business Object Mapping
In the resultant dialog, enter the URL of the desired web service:
125 | P a g e
IdeaBlade DevForce Business Object Mapping
The web service returns information about what Services and Operations it provides:
A .NET class will be generated to facilitate your access of this web service. Change the namespace that will be used
for the code in this class if desired:
This will result in an additional EntityModel node in the navigation pane, and a new container (ObjectContext) with
classes corresponding to the output of the web service. Such classes will become types in your DomainModel, peers
of those generated from database tables.
126 | P a g e
IdeaBlade DevForce Business Object Mapping
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
127 | P a g e
IdeaBlade DevForce Business Object Mapping
<Bindable(true, BindingDirection.TwoWay)> _
<Editable(true)> _
<Display(Name:="Company Name", AutoGenerateField:=true)> _
<IbVal.ValidateProperty> _
<IbVal.StringLengthVerifier(MaxValue:=40, IsRequired:=true)> _
<IbCore.MaxTextLength(40)> _
<MsSer.DataMember> _
Public Property CompanyName() As String
Get
Return CompanyNameEntityProperty.GetValue(Me)
End Get
<System.Diagnostics.DebuggerNonUserCode> _
Set
CompanyNameEntityProperty.SetValue(Me, value)
End Set
End Property
#End Region
See the chapter “Property Interceptors” for a detailed discusion of how you can write code to intercept Gets and Sets
to change the delivered or received value, perform security checks, or perform any desired related operation.
There is even more information in the CompanyNameEntityProperty that we have so far described. As it so happens,
EntityProperty has two subclasses, DataEntityProperty and NavigationEntityProperty, which contain additional
information. Since CompanyName isn‟t a navigation property, but rather a simple data property,
CompanyNameEntityProperty is generated into the designer code as a DataEntityProperty. That has the following
members:
As you can see, the information you have available about the CompanyName property to your interceptor methods
is quite rich indeed. In addition to the things we‟ve seen before, you have the property‟s default value, and you can
tell if its value is autoincremented, if it is a complex type, if it is designated as a property to be checked for the
determination of data concurrency, and if it belongs to its containing object‟s key.
Navigation Properties
Now let‟s look at the definition for a navigation property. These, you may recall, are generated when relations are
defined between types. The Customer type, for example, is involved in a one-to-many relation with the Order type: a
given Customer can place many Orders. So the DevForce Object Mapper generated an Orders property into the
Customer class:
128 | P a g e
IdeaBlade DevForce Business Object Mapping
[Display(AutoGenerateField=false)]
[MsSer.DataMember]
[IbEm.RelationProperty("FK_Order_Customer", IbEm.QueryDirection.ToRole2)]
public IbEm.RelatedEntityList<Order> Orders {
get { return OrdersEntityProperty.GetValue(this); }
}
#endregion Orders property
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).
14
See the “Property Interceptors” chapter to learn how to bypass the getter and setter logic for those cases where you
need to.
129 | P a g e
IdeaBlade DevForce Business Object Mapping
Get
anEmployee.LastName
Employee.LastNameEntityProperty.GetValue(anEmployee)
Set
anEmployee.LastName = “Jones”
Employee.LastNameEntityProperty.SetValue(anEmployee, “Jones”)
Another overload of GetValue() permits you to access the several different versions of value of the property:
Current is the value you retrieve using the two Get syntaxes first shown. Original is the value as last retrieved from
the data source, before any local changes (if any) were made. Just before a change is made to the Current value of
an entity, it has a Proposed value which may or may not be allowed depending upon setter logic. The Default value
depends upon the entity‟s EntityState. For example, the Default value is the Current value for an entity in any of the
following EntityStates: Added, Modified, or Deleted. For an entity in the states EntityState.Detached or
IEditableObject.Edit, the Default value is the Proposed value.
EntityManager.GetEntityGroup
In the cache, entities of a single type are stored in a container called an EntityGroup. You probably won‟t find much
direct need of this container, but it does raise a few low-level events that can be useful in very specific situations,
those being:
EntityPropertyChanging
EntityPropertyChanged
EntityChanging
EntityChanged
The first two fire when a property is changed, and are specific to that property; the last two fire when anything about
an entity changes (including a property). If you‟re alert, you may note that those occurrences seem pretty well
covered by the corresponding interceptors in the property setters that we just finished discussing:
Event on EntityGroup Corresponding Interceptor methods generated into the Entity class
EntityPropertyChanging Before{PropertyName}Set
EntityPropertyChanged After{PropertyName}Set
EntityChanging BeforeSet
EntityChanged AfterSet
And you‟d be right: for most things you need to be in response to a change in an entity, the interceptor methods are
the vehicle of choice. But the EntityGroup events offer one advantage over the latter: because they are implemented
130 | P a g e
IdeaBlade DevForce Business Object Mapping
as event handlers, they can be added and removed dynamically at runtime. If you need to do something conditioned
upon runtime circumstances, they‟ll be just the ticket.
You can get an instance of the EntityGroup for a type (we‟ll use Customer again) from an EntityManager as follows:
C#
EntityGroup customerGroup = anEntityManager.GetEntityGroup(typeof(Customer)) ;
VB
You can also get the EntityGroup associated with a particular entity:
C#
aCustomer.EntityAspect.EntityGroup
VB
Multiple Datasources
Some business object models unite data from multiple data sources. Order information, for example, might reside in
both an application-specific database and in an accounting database. We might need to read from both. We might
need to post to both. We can accommodate these requirements within a single business object model 15.
We began the mapping session illustration by supplying a database connection string. We then examined the
database objects, picked a few, mapped them, and generated their classes.
15
The data sources can be a mix of databases and web services. We illustrate this discussion with database data sources.
131 | P a g e
IdeaBlade DevForce Business Object Mapping
DataSourceKeys
We neglected to mention that the concrete business objects we declared were attached permanently to a
particular DataSourceKey. We associated that key with a database accessible via the connection string. Let’s revisit
the moment after setting that connection string.
Notice the DataSourceKey property associated with the Entity Data Model. In the screen shot, above, the property
has the name “Default”, which is the default value given to this property by the Object Mapper. But this can be
renamed as desired by the developer.
In the Entity Data Model, the DataSourceKey name is stored as a DataSourceKey attribute of the Schema element
within the ConceptualModel. This tells you that there is a one-to-one mapping between a DataSourceKey and a
particular Entity Data Model (EDM). Since a single DevForce DomainModel can encompass multiple EDMs, there
is then a one-to-many relationship between a DomainModel and its DataSourceKeys.
In the app.config file, the name of a DataSourceKey is stored in a name attribute of an edmKey element. In that file,
the Object Mapper generates one edmKey for each Entity Data Model included in the DomainModel. Each edmKey,
besides having a name, also has a connection attribute whose value includes both the location of the entity model
artifacts (.csdl, .ssdl, and .msl files) and the connection string for the database. When the Object Mapper generates
its version of the app.config initially, this connection string is brought over intact from the app.config file associated
with the underlying Entity Data Model.
132 | P a g e
IdeaBlade DevForce Business Object Mapping
You might assume, at first blush, that there is necessarily exactly one edmKey for each DataSourceKey. But that
isn‟t the case. While there can be, at runtime -- at a given moment -- only one physical data source associated with a
given Entity Data Model, DevForce provides you with the flexibility to assign different physical datasources to a
given EDM at different times. For example, you might have Development, Test, and Production versions of a given
database. All have the same schema, but contain different data. You can decide for a given runtime session of the
application which database should be used to supply data to Entity Data Model XYZ. You can even switch the
datasource out dynamically while running!
To use this capability, you add additional edmKey elements manually to the app. config, so that it will end ups with
multiple edmKeys for at least some of the Entity Data Models that comprise your Domain Model. Because of this
capability, the actual formal relationship between Entity Data Models and edmKeys is one-to-many.
On the other hand, there should be exactly one edmKey for each physical database that can contribute data to a
given DomainModel at runtime. Thus the relationship of edmKeys to physical databases is 1-to1.
The relationship between these various elements is summarized in the following sidebar and diagram:
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.
16
NorthwindIB is our IdeaBlade version of the NorthwindEF sample database distributed by Microsoft. We‟ve made a variety of
changes to permit us to illustrate different capabilities of DevForce and the Entity Framework, but the two database remain
substantially similar.
133 | P a g e
IdeaBlade DevForce Business Object Mapping
We‟ll see how each of the three situations is modeled in the Entity Framework, and discuss some pros and cons.
Introduction
“Payload” is the term used by the Entity Framework designers to describe columns in a linking entity other than the
foreign keys to the two external items that the linking entity connects. In the database diagram below,
EmployeeTerritory is the many-to-many linking table between the Employee and Territory tables. It has two foreign
keys, EmployeeID and Territory ID, which link it to those tables (in many-to-1 relationships). But it has also has ID
and RowVersion columns which, whatever their business function, constitute payload in the linking entity.
Figure 2. Linking Table with Payload
The following database diagram, on the other hand, depicts a linking table, EmployeeTerritoryNoPayload, which
has only the two foreign key columns.
Figure 3. Linking Table with no Payload
134 | P a g e
IdeaBlade DevForce Business Object Mapping
As it so happens, the Entity Data Model (EDM) Wizard in Visual Studio, which is launched when you add an
ADO.NET Entity Data Model item to a project, generates very different conceptual models from these two sets of
table schemas. Let‟s have a look.
135 | P a g e
IdeaBlade DevForce Business Object Mapping
Notice the many-to-1 associations17 between EmployeeTerritory and Employee, and between EmployeeTerritory
and Territory. Taken together, they define a many-to-many association between Employee and Territory, but that
association is not explicit.
Notice also that the EDM wizard created a navigation property in the Employee entity to return the employee‟s
collection of associated EmployeeTerritory objects. It named that property EmployeeTerritory; we renamed it to
“EmployeeTerritories” to make clearer that it returns a collection.
The wizard created a corresponding navigation property in the Territory entity, which we also renamed.
If we want the collection of Territory entities that are associated with a particular Employee entity, we‟ll either have
to iterate through its collection of EmployeeTerritory entities, grabbing the Territory associated with each and
setting it aside in a list; or write a query to retrieve them. Whatever operation we choose to use to compile the list we
can of course embed in a property or method of our Employee class to give us convenient access to the desired
associates.
17
If you‟re new to the Entity Framework and/or to object modeling, just be aware that what are called “relationships” between
tables in a database are referred to as “associations” between the corresponding objects in an Entity Framework conceptual
model. For practical purposes the terms “relationship”, “relation”, and “association” frequently get used more or less
interchangeably in discussions of these object models, even though, technically, in UML terminology, an association is just
one subtype of relationship.
In this paper I‟ve used the term “relationship” when speaking of tables in a database, and “association” when speaking of entities
in an object model. I attempt no finer distinction than that.
136 | P a g e
IdeaBlade DevForce Business Object Mapping
As it turns out, the EDM code generator decided that its function was entirely to associate the Employee and
Territory entities, and that it therefore needed no explicit presence in the model. Instead, it chose simply to describe
the many-to-many association between Employee and Territory.
It also created a navigation property in the Employee entity to return the collection of associated Territories. Since it
doesn‟t attempt meaningful pluralization, it named this property “Territory”; we renamed it to “Territories”.
Corresponding, it created a navigation property in the Territory entity to return the related Employees. This, of
course, it named “Employee”; we renamed it to “Employees”.
The Issues
While the invisible linking entity used for the payload-free linking table has its attractive aspects, it also means that
you must necessarily live with two different ways of modeling many-to-manys in your Entity Data Model. You
can‟t always live without a payload in your linking entity. Consider, example, an Order entity that links Sales
Representatives to Customers in a many-to-many association. The Order is important in its own right, and is likely
to carry a great deal of important informational baggage. It certainly must be exposed in your business model,
whatever its function as a linking entity.
The other issue is that “pure”, payload-free linking entities sometimes, over their lifetimes, need to grow a payload.
You may find that you wish to record certain pieces of information about the association itself. When was said
Territory assigned to said Employee? Who made the assignment? Why was the assignment made?
The moment you add payload, you will have to change your model, because the linking entity can, by the rules of
the ADO.NET Entity Data Model, no longer remain unexposed. Furthermore, the explicit many-to-many association
it formerly defined is no longer supported. So you will have to rewire that many-to-many association as a pair of
many-to-1s. This isn‟t terribly hard if you know what you‟re doing with the EDM, but we would certainly advise
you to practice the job offline, in a small test model, before you try it on your real EDM. And make a backup of the
latter before you start hacking away at it. It‟s all too easy to hose it up, at which point you will have no choice but to
don your swamp boots and head bravely into a mosquito-infested swamp of XML.
18
all in the eye of the beholder, of course
137 | P a g e
IdeaBlade DevForce Business Object Mapping
VB
The next 30 of these look pretty much like this one: I just substitute the appropriate entity types and key properties.
138 | P a g e
IdeaBlade DevForce Business Object Mapping
The unfortunate thing is that this is not an option provided by the EDM designer. You‟ll have to do a bit of
twiddling. The dirty details would make this article too long and take it off course, but here‟s a prescription by
which you can figure them out yourself.
You can do everything below, except the final step, in the EDM designer:
1. Create a linking entity that has a payload (anything!) and build a model that uses it. The best model for this
purpose is probably one that contains exactly three entities, like the ones we‟ve looked at in this article.
2. Remove the payload columns from the linking entity‟s backing database table.
3. Update the EDM using its “Update Model from Database” option.
4. Delete the properties corresponding to the payload column or columns that you removed.
5. Add the two foreign key properties to the conceptual in the EDM as explicit scalar properties.
6. Designate the two foreign key properties as primary keys.
7. Flesh out the table mappings for the two newly added properties, and for the two many-to-1 associations.
8. Examine the Before and After model to see what‟s different.
If you‟re free to change the database on which the model is built, you might be able to use the above technique
directly on your actual development model. Otherwise you‟ll have to spend enough energy on the last step above to
be able to reproduce the result in your model. It‟s not rocket science, but it‟s not quite falling off a log, either.
139 | P a g e
IdeaBlade DevForce Property Interceptors
Property Interceptors
Property Interceptors
Named vs. Unnamed Interceptor Actions
Interceptor Chaining and Ordering
DevForce provides a mechanism to intercept and either modify or extend the behavior of any .NET property. This
interception is intended to replace, and expand upon, the technique of marking properties as virtual and overriding
them in a subclass. This facility is a lightweight form of what is termed “Aspect-Oriented Programming”.
Interception can be accomplished either statically, via attributes on developer-defined interception methods, or
dynamically, via runtime calls to the „current‟ instance of the PropertyInterceptorManager (described later).
Attribute interception is substantially easier to write and should be the default choice in most cases.
Attribute Interception
DevForce supplies four attributes that are used to specify where and when property interception should occur.
These attributes are
IdeaBlade.Core.BeforeGetAttribute
IdeaBlade.Core.AfterGetAttribute
IdeaBlade.Core.BeforeSetAttribute
IdeaBlade.Core.AfterSetAttribute
Under most conditions these attributes will be placed on methods defined in the custom partial class associated with
a particular DevForce entity. For example, the code immediately below represents a snippet from the autogenerated
Employee class.
(Generated code)
Property interception of the get portion of this property would be accomplished by adding the following code
fragment to a custom Employee partial class definition:
(Developer code)
140 | P a g e
IdeaBlade DevForce Property Interceptors
C# [AfterGet(EntityPropertyNames.LastName)]
public void UppercaseLastName(PropertyInterceptorArgs<Employee, String> args) {
var lastName = args.Value;
if ( !String.IsNullOrEmpty(lastName)) {
args.Value = args.Value.ToUpper();
}
}
VB <AfterGet(EntityPropertyNames.LastName)> _
Public Sub UppercaseLastName(ByVal args As PropertyInterceptorArgs(Of Employee, String))
Dim lastName = args.Value
If Not String.IsNullOrEmpty(lastName) Then
args.Value = args.Value.ToUpper()
End If
End Sub
DevForce will insure that this method is automatically called as part of any call to the Employee.LastName „get‟
property. The “AfterGet” attribute specifies that this method will be called internally as part of the „get‟ process
“after” any internal get operations involved in the get are performed. The effect is that the LastName property will
always return an uppercased result. For the remainder of this document, methods such as this will be termed
interceptor actions.
The corresponding „set‟ property can be intercepted in a similar manner.
C# [BeforeSet(EntityPropertyNames.LastName)]
public void UppercaseLastName(PropertyInterceptorArgs<Employee, String> args) {
var lastName = args.Value;
if ( !String.IsNullOrEmpty(lastName)) {
args.Value = args.Value.ToUpper();
}
}
VB <BeforeSet(EntityPropertyNames.LastName)> _
Public Sub UppercaseLastName(ByVal args As PropertyInterceptorArgs(Of Employee, String))
Dim lastName = args.Value
If Not String.IsNullOrEmpty(lastName) Then
args.Value = args.Value.ToUpper()
End If
End Sub
In this case we are ensuring that any strings passed into the „LastName‟ property will be uppercased before being
stored in the Employee instance ( and later persisted to the backend datastore). Note that, in this case, the
interception occurs “before” any internal operation is performed.
In these two cases we have implemented an „AfterGet‟ and a „BeforeSet‟ interceptor. BeforeGet and AfterSet
attributes are also provided and operate in a similar manner.
141 | P a g e
IdeaBlade DevForce Property Interceptors
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
C# [AfterGet(EntityPropertyNames.LastName)]
public void UppercaseLastName(PropertyInterceptorArgs<Employee, String> args) {
/// … do something interesting
}
142 | P a g e
IdeaBlade DevForce Property Interceptors
<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.
143 | P a g e
IdeaBlade DevForce Property Interceptors
C# [BeforeSet(EntityPropertyNames.FirstName)]
[BeforeSet(EntityPropertyNames.LastName)]
[BeforeSet(EntityPropertyNames.MiddleName)]
public void UppercaseName(PropertyInterceptorArgs<Employee, String> args) {
var name = args.Value;
if ( !String.IsNullOrEmpty(name)) {
args.Value = args.Value.ToUpper();
}
}
VB <BeforeSet(EntityPropertyNames.FirstName), BeforeSet(EntityPropertyNames.LastName),
BeforeSet(EntityPropertyNames.MiddleName)> _
Public Sub UppercaseName(ByVal args As PropertyInterceptorArgs(Of Employee, String))
Dim name = args.Value
If Not String.IsNullOrEmpty(name) Then
args.Value = args.Value.ToUpper()
End If
End Sub
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.
DataEntityPropertyGetInterceptorArgs<TInstance, TValue>
DataEntityPropertySetInterceptorArgs<TInstance, TValue>
144 | P a g e
IdeaBlade DevForce Property Interceptors
NavigationEntityPropertyGetInterceptorArgs<TInstance, TValue>
NavigationEntityPropertySetInterceptorArgs<TInstance, TValue>
The boldfaced characters above indicate whether we are providing interception to a get or a set property, as well as
whether we are intercepting a data or a navigation property.
In general, you can write an interception method with an argument type that is any base class of the actual argument
type defined for that interceptor. If you do use a base class, then you may need to perform runtime casts in order to
access some of the additional properties provided by the specific subclass passed in at runtime. These subclassed
properties will be discussed later.
The entire inheritance hierarchy for property interceptor arguments is shown below:
PropertyInterceptorArgs<TInstance, TValue>
DataEntityPropertyInterceptorArgs<TInstance, TValue>
IdeaBlade.EntityModel
DataEntityPropertyGetInterceptorArgs<TInstance, TValue>
DataEntityPropertySetInterceptorArgs<TInstance, TValue>
NavigationEntityPropertyInterceptorArgs<TInstance, TValue>
NavigationEntityPropertyGetInterceptorArgs<TInstance, TValue>
NavigationEntityPropertySetInterceptorArgs<TInstance, TValue>
The generic <TInstance> argument will always be the type that the intercepted method will operate on, known
elsewhere in this document and the interceptor API as the “TargetType”. The <TValue> argument will be the type
of the property being intercepted. i.e. „String‟ for the „LastName‟ property.
Note that the interceptor arguments defined to operate on DevForce entities break into multiple subclasses with
additional associated interfaces based on two primary criteria.
1) Is it a „get‟ or a „set‟ interceptor?
a. „get‟ interceptor args implement IEntityPropertyGetInterceptorArgs
b. „set‟ interceptor args implement IEntityPropertySetInterceptorArgs
IPropertyInterceptorArgs
The root of all property interceptor arguments is the IPropertyInterceptorArgs interface. Its properties will be
available to all interceptors.
145 | P a g e
IdeaBlade DevForce Property Interceptors
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.
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
146 | P a g e
IdeaBlade DevForce Property Interceptors
Generic IPropertyInterceptorArgs
The following is a generic version of IPropertyInterceptorArgs where both the Instance and Value properties are
now strongly typed; otherwise it is identical to IPropertyInterceptorArgs.
147 | P a g e
IdeaBlade DevForce Property Interceptors
C# [AfterSet]
public void AfterSetAny(IPropertyInterceptorArgs args) {
var entityPropertyArgs = args as IEntityPropertyInterceptorArgs;
if ( entityPropertyArgs != null) {
Log(“The “ + entityPropertyArgs.EntityProperty.Name + “ was set to the value: “ +
args.Value.ToString());
}
}
VB <AfterSet> _
Public Sub AfterSetAny(ByVal args As IPropertyInterceptorArgs)
Dim entityPropertyArgs = TryCast(args, IEntityPropertyInterceptorArgs)
If entityPropertyArgs IsNot Nothing Then
Log(“The “ + entityPropertyArgs.EntityProperty.Name + “ was set to the value:= “ +
args.Value.ToString())
End If
End Sub
The next two interfaces provide additional context based on whether the interceptor action being performed is a „get‟
operation or a „set‟ operation.
For a get operation, IdeaBlade entities have a concept of possibly multiple versions, i.e. an original, current, or
proposed version, of an entity at any single point in time. It may be useful to know which „version‟ is being
retrieved during the current action. Note that the version cannot be changed.
For a set operation, IdeaBlade has as part of its underlying implementation of any property the idea of possibly
validating ( verifying) the incoming data. The VerificationSetterOptions property of any implementation of
IEntityPropertySetInterceptorArgs provides the ability to determine whether a validation has or will be called
as well as allowing any „BeforeSet‟ code to actually change how the verification will occur.
public interface IEntityPropertySetInterceptorArgs : IEntityPropertyInterceptorArgs {
VerificationSetterOptions VerificationSetterOptions { get; set; }
}
An example:
C# [AfterSet]
public void BeforeSetAny(IEntityPropertySetInterceptorArgs args) {
// turn off validation
args.VerificationSetterOptions = VerificationSetterOptions.None;
}
VB <AfterSet> _
Public Sub BeforeSetAny(ByVal args As IEntityPropertySetInterceptorArgs)
' turn off validation
args.VerificationSetterOptions = VerificationSetterOptions.None
End Sub
148 | P a g e
IdeaBlade DevForce Property Interceptors
The DevForce EntityProperty is an abstract class with two concrete subclasses; a DataEntityProperty and a
NavigationEntityProperty ( discussed elsewhere in this guide). The next two IEntityPropertyInterceptorArgs
subinterfaces allow access to instances of one or the other of these depending on whether the property being
intercepted is a data or a navigation property.
VB <BeforeSet(EntityPropertyNames.HireDate), BeforeSet(EntityPropertyNames.FirstName)> _
Public Sub StrangeAction(ByVal args As IPropertyInterceptorArgs)
Dim emp = CType(args.Instance, Employee)
Dim entityProperty = (CType(args, IDataEntityPropertyInterceptorArgs)).EntityProperty
.. do some very baroque operation with emp and entityProperty
End Sub
But ideally we would prefer to write it like this, in order to avoid performing a lot of superfluous casts:
149 | P a g e
IdeaBlade DevForce Property Interceptors
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>
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:
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.
150 | P a g e
IdeaBlade DevForce Property Interceptors
[AfterSet]
public void LoggingInterceptor(IEntityPropertyInterceptorArgs args) {
…
}
}
<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:
[AfterSet(User.EntityPropertyNames.Name, TargetType=typeof(User)]
public void LoggingInterceptor(IEntityPropertyInterceptorArgs args) {
…
}
}
<AfterSet(User.EntityPropertyNames.Name, TargetType:=GetType(User)> _
Public Sub LoggingInterceptor(ByVal args As IEntityPropertyInterceptorArgs)
…
End Sub
End Class
151 | P a g e
IdeaBlade DevForce Property Interceptors
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
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.
152 | P a g e
IdeaBlade DevForce Property Interceptors
153 | P a g e
IdeaBlade DevForce Property Interceptors
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)
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)
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:
154 | P a g e
IdeaBlade DevForce Property Interceptors
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.
155 | P a g e
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
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.
One of them, called an EntityServer, moves data (and data requests) between the ADO.NET Entity Framework
and DevForce business objects. If the back-end data store is a relational database, the EntityServer leaves the direct
communication with the data store to the ADO.NET Entity Framework. However, if the back-end data store is a web
service, the DevForce EntityServer handles the job, since that capability does not exist within the Entity Framework.
The EntityServer has a copy of the application‟s business object model so that it can instantiate DevForce
business objects server-side if need be. However, for most operations (such as simple data retrievals), it forwards to
the client-side EntityManager the data required for hydrating DevForce business objects there, without ever
instantiating DevForce business objects on the server. The data is packaged and passed in a highly efficient format
and process.
The ADO.NET Entity Data Model includes the mapping information necessary to translate between locations in a
relational data source and the corresponding persistent fields in the ADO.NET business entities. The
EntityServer (besides handling those jobs against web services), mediates between the Entity Framework and
the DevForce EntityManager that manages the client-side cache used by your application.
The EntityServer is an important component and you should understand its role in the object persistence
process. That said, you will seldom see or deal with it directly.
The second important DevForce component is the EntityManager. The EntityManager takes instruction from
the higher levels of the application such as the UI, and forwards UI requests for entities to the EntityServer. The
EntityManager puts the received entities – obtained from whatever source by the EntityServer -- into its
entity cache and makes them available to the UI.
End users review the entities and make changes through the UI. The UI signals the EntityManager to save the
changes. The EntityManager dutifully forwards the changed entities to the EntityServer which communicates
with the appropriate component to commit the data into persistent storage.
IdeaBlade DevForce Business Object Persistence
For those familiar with DevForce Classic (mated with .NET 2.0): the Entity Framework model essentially
takes over the function handled in DevForce Classic by the .ORM file. Both contain knowledge of the data
source and mapping information.
A copy of the assembly containing the Domain model is also deployed server-side in an n-tier deployment.
Architecture of the DevForce Business Object Class
The (partial) inheritance hierarchy for a DevForce business class is as follows:
IdeaBlade DevForce Business Object Persistence
The class for a business type is generated as one or two partial classes. In the partial classes labelled in the picture as
DevForce-controlled, the essential data structure of the type is defined. This partial class is driven by settings in the
domain model and gets regenerated whenever the develop instructs the DevForce Object Mapper to regenerate code.
Thus it should never be modified by the developer.
All DevForce-controlled partial classes for types
originating from a given Entity Data Model are generated
into a single file, named
<DomainModelName>.<EntityModelName>.Designer.cs
(or .vb, if generated in Visual Basic rather than C#). For
example, the code file for the ServerModelNorthwindIB
Entity Data Model of a domain model named
DomainModel generated in C# would be named
DomainModel. ServerModelNorthwindIB.Designer.cs, as
shown at right.
If the domain model includes multiple Entity Models,
one such code file will be generated for each model, as
shown at left.
The partial class described in Figure 5 as “Developer-controlled” is optional, and can be generated by the Object
Mapper in a one-time operation, or hand-coded by the developer. In either case, once it exists, the Object Mapper
will not overwrite or modify it. The developer-controlled partial class is the developer‟s workshop, where he can add
custom properties, methods, and events, as well as create property interceptors 19 to change the getter/setter behavior
of properties defined in the DevForce-controlled partial class.
If the developer asks the Object Mapper to generate
developer partial classes, it will generate one such
class for each type in the domain model. Each such
partial class will be generated into its own file, which
bears the name of the type. You can see this at right.
Again, these files are generated by the Object Mapper only when they do not already exist, and are not touched
subsequently. Thus the developer can safely add her own code to this file without fear that it will be overwritten.
19
See the Developers Guide chapter on “Property Interceptors”
IdeaBlade DevForce Business Object Persistence
If you are already familiar with the Entity Framework, you will note that DevForce code generation proceeds
according to the same pattern used by the Entity Framework. The Entity Framework also generates partial classes
for each type in a model, and all into a single class. It does not generate partial classes for developer work, but does
permit the developer to create and maintain such partial classes.
POCOs are discussed in detail later in this chapter (“POCO Support in DevForce”).
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.
20
DevForce keeps a cache of query objects for use in determining whether requested objects are already in the cache; we‟ll
cover this in more detail later.
21
An application can actually have more than one EntityManager instance, though this is a needed only in sophisticated
applications and for special purposes. Each EntityManager instance will have its own cache, and each cache can contain an
instance of any given business object. But every entity instance knows its own EntityManager. If we ever encounter two
Employee entities with Id = 42, we can ask them “who is your EntityManager?”
For more information on the use of multiple EntityManagers, see the section “Multiple Entity Manager Instances” under
“Advanced Business Object Concepts”. For the balance of the current discussion, we will assume the application uses just
one EntityManager instance.
IdeaBlade DevForce Business Object Persistence
22
DevForce also provides a facility known as “checkpointing” that provides a transaction facility for operations in the local
cache. Checkpointing gives you the ability to undo changes back to a specified state, perhaps not so far back as the state
when retrieved from the data source. The utility of “checkpointing” is most apparent in the UI so we cover it in the WinForm
User Interfaces chapter in the topic “Multi-Level Undo with Checkpoints”.
IdeaBlade DevForce Business Object Persistence
Offline Support
A client application can lose its connection to the central servers. The interruption may be brief, sudden, and
unexpected, as when a mobile device loses its signal; or it may be voluntary and last for hours, as when the user runs
the application offline on an airplane.
A DevForce smart client application can operate when disconnected -- whether suddenly and unexpectedly or on
purpose -- for any length of time. It can be shut down and re-started without skipping a beat.
While disconnected, the application can still create new objects and modify or delete cached entities. Such changes
accumulate in the cache until the application reconnects and performs a save.
All it takes is a little programming using some simple DevForce EntityManager features.
Step #1: Manage the connection. The developer can control voluntary connection to the host and respond to
unexpected disconnects with the help of a small number of EntityManager properties, methods, and events.
Step #2: Save a copy of the cache locally. The typical sequence is:
1. Fill the cache with entities that will be needed while running disconnected.
2. Disconnect and continue running.
3. Save the cache to the client‟s local storage (e.g., a file) just before exit.
4. Shut down.
5. On re-launch, restore the cache from the client copy.
All pending changes are preserved across the two sessions.
See the “Saving the Cache Locally” section of the “Business Object Caching” chapter to learn more.
Application Security
We‟ll devote a later chapter to securing your application, so we‟ll just mention the topic briefly in this overview.
Application security has three aspects:
Confidentiality
Authentication
Authorization
Confidentiality – A secure application guards against eavesdroppers intercepting and reading traffic flowing
between client and host. DevForce supports a variety of encryption measures including standard SSL. They are
discussed in the Security chapter of this Developers Guide.
Authentication – A secure application employs an authentication scheme to ensure that both parties to a connection
are who they claim to be. In a smart-client context there are two authentication burdens: (1) the server must confirm
and remain confident it is talking to a real, authorized client and (2) each client must be confident it is conversing
with an authentic server. DevForce has mechanisms to support both kinds of checks.
Authorization –The EntityManager‟s Login method stamps the client-side application thread with a Principal
object representing the authenticated user. This Principal has an IsInRole method that returns true if the user
participates in a named role passed to it. The developer has total flexibility in determining the implementation of the
login method, the IPrincipal object returned from it, and the definition and usage of the role scheme.
IdeaBlade DevForce Business Object Persistence
For its own part, DevForce maintains a tamper-proof SessionBundle object that is used to authenticate every
transaction between the EntityManager and EntityServer.
Spoofing
In n-tier applications, whether browser-based or smart client, there is always a risk that some process pretending to
be a valid client will attempt access the database in an unauthorized way. A good security design assumes that the
client process -- because it cannot be physically secured -- will be compromised.
While it may not be possible to fully protect the client, you can secure the host by deploying the DevForce Business
Object Server (BOS) which includes the full-scale version of the EntityServer. The BOS will run special security
methods whenever the client attempts to reach the server.
As discussed above, the EntityServer includes ServerFetching, ServerFetched, ServerSaving, and
ServerSaved events. You can handle the ServerFetching event to intercept data retrieval requests and the
ServerSaving event to intercept save requests, in each case before-the-fact, to make sure the authenticated user
has rights to do what she is requesting.
These handlers run server-side, and no client can prevent the server from invoking them. Furthermore, your handlers
can delegate their work to other methods that exist in libraries only deployed to the server. No hacker can examine
the latter, so your application can be made safe from disassembly and spoofing.
Finally, DevForce business objects can be digitally signed before transmission to the client. A rogue client cannot
order the server to update the data source with an imposter entity.
N-Tier Architecture
We discussed n-tier architecture at the beginning of this chapter. “The Big Picture” topic described three data
management tiers:
6. Data source(s) on the data tier
7. EntityServer(s) as the data access tier
8. EntityManager within a client tier
You can run all three logical tiers on the client machine if you have a totally stand-alone application. This is the
preferred choice for most development work because it eliminates the complexities of coordinating with other
people, software, and hardware.
When a data-driven application is deployed for production use, the database must reside on a central tier so that
many users can share the data efficiently. If, with the database so deployed, you put both the EntityManager and
IdeaBlade DevForce Business Object Persistence
an EntityServer in the same process running on a client PC, you have the ever popular two-tier, “client/server”
model.
This simplifies the exchanges between an EntityServer and the EntityManager. The two components don‟t
have to communicate over a process boundary, so in a DevForce application deployed thusly, a light-weight version
of the EntityServer reads and writes directly to the EntityManager cache.
An EntityServer running under such circumstances cannot provide any meaningful security or monitoring
services. It serves simply a data access abstraction – a job it does very well.
Three-Tier Deployment
Enterprise-grade applications will deploy the logical tiers on three separate physical tiers: a database server, an
application server, and PC client machines. The application server hosts the Business Object Server (BOS) which
runs multiple instances of a more muscular version of the EntityServer.
This three-physical-tier deployment provides some remarkable advantages over the two-tier model. You get:
Improved performance over connections slower than a local area network (e.g., the internet). The slow, heavy work
takes place between the BOS and the database over a fat, fast pipe. Communications and data passing between the
client and the middle tier are concise, compact, and highly optimized.
Application Reach -- Because the application can be on-line wherever there is an Internet connection and without
resort to VPN, it can be deployed and used by larger numbers and with reduced system requirements. Whereas SQL
commands and result sets – the raw data exchanged between a database and a client-side access layer – cannot flow
over web protocols, DevForce‟s business objects can.
Security is much tighter. We covered earlier the many layers of security available with the BOS in place.
Scalability. It is impractical to maintain live connections for each client when the number of simultaneous users
becomes large. The tipping point appears to be around one hundred. An EntityServer running on a central server can
pool connections to the data sources and serve many clients simultaneously. The server is stateless – there is no need
for session awareness – so fail-over and load balancing are easy options.
The BOS monitoring console provides detailed data and global insight into the use (and abuse) of the application.
Conclusion
We just took a high-level view of the persistence management landscape. Some of the key points were:
The EntityManager is perhaps the most important component in the DevForce framework. It is the client
application‟s gateway to the remote data.
The EntityManager holds and manages an entity cache of business object instances and makes them
available to the application UI.
All entities within a cache are unique; no two entities can have the same primary key.
You can fetch entities into the cache from remote data sources using entity queries and entity navigation.
IdeaBlade DevForce Business Object Persistence
Entity navigation returns a collection whose contents are managed dynamically by a EntityManager.
You can create, modify, delete, remove, and save cached entities. These actions raise “Life Cycle” events
to which you may subscribe.
Entities in a cache can come from many different data sources. Each data source is identified by its data
source key. Each entity belongs to just one data source.
A smart-client application can run off-line.
An EntityServer handles the data access and object map translation chores for each of the application
data sources. It exchanges business objects with one or more EntityManager instances on individual
client machines.
A Business Object Server (BOS) running on a central host provides enterprise-grade security, scalability,
data integrity, performance, and application monitoring.
The following sections and chapters delve deeper into the features introduced here.
Entity Queries
Use an EntityQuery when you want to retrieve a set of business objects that satisfy selection criteria - the set of
employees who were hired last year, for example.
Entity queries come in many flavors. Some of them are linguistically independent of any particular data source;
some are specialized to a particular data source. Some can query the data source and the entity cache at the same
time; some can only query the data source23.
EntityQueries, like .NET ObjectQueries, are enumerable, and so can be executed in a variety of stepwise ways.
Consider, for example, the following query:
Code Snippet 1. BasicQuerySyntaxQuery
C# var customersQuery =
from cust in _Em1.Customers
where cust.ContactTitle == "Sales Representative"
orderby cust.CompanyName
23
This means this kind of query can be used only when the application is connected to the data source; such queries can‟t run
when the application is off-line.
IdeaBlade DevForce Business Object Persistence
select cust;
VB Dim customersQuery =
From cust In _em1.Customers _
Where cust.ContactTitle = "Sales Representative" _
Order By cust.CompanyName _
Select cust
Or just,
Code Snippet 3. MethodSyntaxShortForm
24
Query-based syntax looks a great deal like SQL and is, for that reason, attractive to many developers, especially those new to
LINQ. At IdeaBlade we tend to prefer the more regularly structured and comprehensive method-based syntax for most
queries, so you will see most of our sample queries in that format. Be assured, however, that you may write your LINQ
queries in the syntax you prefer!
We discuss the two syntaxes more in the section “Query v. Method Syntax”, in this document.
IdeaBlade DevForce Business Object Persistence
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
The addition of a call to extension method ToList() forces DevForce to execute the query immediately:
Code Snippet 6. ForceImmediateExecution
The call to ToList(), because it demands a complete set of pointers to the retrieved matching customers, forces the
complete query to be executed. Below is a DevForce DebugLog listing for a test that first issued a First() call like
the one we just considered, then a call to ToList(). We‟ve removed some of the columns included in the log so the
table won‟t be quite so wide, but note the highlighted “Fetch … value” messages. The first one, when delivered to
the EntityFramework, will be translated into a SQL query that returns a single record; the second will be translated
into a SQL query that returns all of the matching customers.
IdeaBlade DevForce Business Object Persistence
If you want to see the SQL generated by the EntityFramework to process your query, find the appropriate edmKey
in your App.Config file and add a logTraceString attribute set to “true”:
This will result in output like the following. (Again, some columns were omitted to reduce the table width for
inclusion here.) Note the generated SQL statements:
IdeaBlade DevForce Business Object Persistence
In between the two extremes of asking a query object for its first element and asking it to dump its contents ToList()
are many possibilities, such as using it in a foreach loop:
The foreach loop returns references to the retrieved Customers one at a time, but it does so from a collection of
those references which must be obtained up front. Thus, as soon as the first iteration of the loop is executed, the
IdeaBlade DevForce Business Object Persistence
entire set of Customers is retrieved to the local cache, and a collection of references to them is assembled. The
debug log will show only a single query:
On the other hand, the following query results in exactly five (5) entities being retrieved from the data source:
Code Snippet 8. QueryWithSkipAndTake
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 next two examples use With() to run the query with a different QueryStrategy.
// You can pass null arguments to With(). When a query has a null EntityManager,
// assigned, it uses the DefaultManager. When a query has a null QueryStrategy,
// it uses the DefaultQueryStrategy of the assigned (or default) EntityManager.
// Run the query against the default EntityManager, using its default QueryStrategy:
IEntityQuery<Customer> query5 = query0.With(null, null);
// When you pass a single null to With, you must cast it to the appropriate
// type so the compiler know's which single-parameter overload you mean to use:
// Run the query against the default EntityManager, using the base query's
// assigned QueryStrategy:
IEntityQuery<Customer> query6 = query0.With((DomainModelEntityManager)null);
// Run the query against the assigned EntityManager, using that EntityManager's
// default QueryStrategy:
IEntityQuery<Customer> query7 = query0.With((QueryStrategy)null);
IdeaBlade DevForce Business Object Persistence
' 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
' 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))
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.
Here, from a Customer entity, we have created a query that will retrieve that same Customer. We have then
extended with a call to Include() it to create a span query that will also retrieve all of that Customer‟s associated
Orders. We do not otherwise have so convenient a way to accomplish this goal.
The ToQuery() extension method is also provided on any IEnumerable<T> collection, when T is an Entity. Thus
you can turn an arbitrary list of Customers into a query that will return the same set of Customers. The Where()
IdeaBlade DevForce Business Object Persistence
clause on the resultant query will specify a series of OR‟d key values. For example, consider the following
statements:
Code Snippet 11. UsingToQueryPt02
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.
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
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();
Having said that, there are a few things that are arguably a bit easier or more natural to do in query syntax 25, and of
course there are simply personal preferences. So use what you like!
LINQ
The typical data-oriented approach to retrieving objects relies upon a specialized query language such as SQL. SQL
is a powerful query language requiring considerable sophistication and experience to use properly. But there are
pitfalls to using SQL and several good reasons to prefer LINQ to SQL queries, including:
Object orientation
Compile time checking
Query portability
Query manipulation
LINQ is a vast subject and is, for the most part, beyond the scope of this document. A web search on “LINQ” will
provide you with an abundance of excellent resources for learning about LINQ.
It suffices to say here that our implementation of LINQ -- LINQ to DevForce -- permits the same query to be used
against a local cache or a back-end datasource supported by Microsoft‟s LINQ to Entities. You can specify, by
means of a QueryStrategy property on the query object, just what you want its target data store or data stores to be;
or you can let DevForce apply sensible defaults which work well for the majority of cases.
25
Joseph and Ben Albahari, in a fine discussion of LINQ, opine that query comprehension syntax “is much simpler for queries
that involve any of the following:
A let clause for introducing a new variable alongside the iteration variable
SelectMany, Join, or GroupJoin, followed by an outer iteration variable reference”
See their excellent book C#3.0 In a Nutshell, O‟Reilly Media Inc., 2007, p.285
IdeaBlade DevForce Business Object Persistence
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:
Now all you have to do is replace the “Where” clause with a sequence of OR clauses. You‟ll want to construct it by
iterating over the words. Go ahead and write it. We‟ll wait...
Having trouble? I‟ll give you the user‟s input: “Sir Cajun Louisiana”. Did that help?
You will probably come up with something like the following:
IdeaBlade DevForce Business Object Persistence
C# var q = _em1.Products
.Where(p =>
p.ProductName.Contains("Sir") ||
p.ProductName.Contains("Cajun") ||
p.ProductName.Contains("Louisiana")
);
var results = q.ToList(); // returns 6 Northwind products
VB Dim q = _em1.Products.Where(Function(p) _
p.ProductName.Contains("Sir") _
OrElse p.ProductName.Contains("Cajun") _
OrElse p.ProductName.Contains("Louisiana"))
Dim results = q.ToList() ' returns 6 Northwind products
What‟s a “predicate”?
A “predicate” is a function that evaluates an expression and returns true or false.
The code fragment...
C# p.ProductName.Contains(“Sir”)
/VB
...is a predicate that examines a product and returns true if the product‟s ProductName contains the “Sir” string.
The CLR type of the predicate in our example is:
C# Func<Product, bool>
C# Func<T, bool>
VB Func(Of T, Boolean)
IdeaBlade DevForce Business Object Persistence
We almost have what we want. When the compiler sees an example of this kind of thing, it immediately resolves it
into an anonymous delegate. But we don‟t want the delegate. We need a representation that retains our intent and
postpones the resolution into a delegate until the last possible moment; because before we get that delegate, we may
want to build a more complex expression. What we need is an expression made up of Func<T, bool>‟s:
C# Expression<Func<T, bool>>
As it so happens, this is exactly what the DevForce “Where” extension method demands:
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
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():
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:
PredicateBuilder Methods
There are seven methods of interest:
IdeaBlade DevForce Business Object Persistence
And p1.And(p2)
False PredicateBuilder.False()
Not PredicateBuilder.Not(p1)
Examples
Here are some examples using the PredicateBuilder methods:
Code Snippet 22. PredicateBuilderMiscExamples
// Name contains "Cajun" and "Lousiana" and the price is greater than 20
bigP = PredicateBuilder.And(p2, p3, p4);
// Not useful
bigP = PredicateBuilder.True<Product>().Or(p1);// always true
bigP = PredicateBuilder.False<Product>().And(p1);// always false
' Name contains "Cajun" and "Lousiana" and the price is greater than 20
bigP = PredicateBuilder.And(p2, p3, p4)
' Name contains either "Sir" or "Louisiana" AND price is greater than 20
bigP = p1.Or(p3).And(p4)
The True() and False() methods return Predicate Expression constants that simply help you jumpstart your chaining
of PredicateBuilder expressions. Two of the combinations – True()…Or() and False()…And() -- are not useful.
IdeaBlade DevForce Business Object Persistence
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();
…
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()
…
The PredicateBuilder versions retrieves exactly the same set of entities into the cache as the hard-coded version.
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.
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:
C# using System.Linq.Expressions;
...
VB Imports System.Linq.Expressions
...
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.
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());
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())
' 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())
PassthruESQL Queries
DevForce supports queries in Entity SQL (ESQL) with its PassThruEsqlQuery() method.
Code Snippet 30. EsqlBasic
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
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.
An asynchronous version of the Remote Service Method Call is also provided. It‟s perfect for any time-
consuming, server-based operation whose results are not needed immediately for continued work in the
client application. The asynchronously RSMC can, for example, be used to load huge and even unrelated
collections of data from the backend data store to the local cache without freezing the UI. The end user
continues productive work while the data is being loaded; and then subsequently enjoys extremely crisp
response in all aspects of the client application that depend upon the data that was loaded, which is now
available directly from the local cache.
Entity Navigation
Entity navigation is a convenient syntax for accessing data from related business objects. Consider these familiar
scenarios:
Get all of a particular sales rep‟s orders.
Find the employee‟s home address
Calculate the sales tax for an order
26
We can extend some Passthru queries to search the cache. See “Advanced Business Object Concepts.”
27
RPC is not an “entity query” facility because it is not required to return entities.
IdeaBlade DevForce Business Object Persistence
In each instance, we want information (orders, address, sales tax table for the ship-to-address) related to a single
entity (salesrep, employee, order). The desired information exists somewhere in the entity‟s business object graph –
the network of other entities that are related to our primary entity.
In DevForce, you can begin with an entity – arbitrarily designated the “root entity” – and traverse its relations to
reach other entities, both near and far. We call this “navigating the graph.”
All you do is write a simple navigation property expression such as myOrder.Customer.
Observe that the navigation property syntax, myOrder.Customer, looks just like one of the entity‟s simple
properties, myOrder.ShippedDate. The key difference is that it returns an entity (Customer) rather than a value
(DateTime).
Entities have properties so you can write myOrder.Customer.Name. They have navigation properties so you can
walk further along the graph to the HeadquartersAddress entity where you‟ll find the headquarters city:
myOrder.Customer.HeadquartersAddress.City
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
We decide to increase the quantity ordered for the first OrderDetail as follows.
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…
…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);
...
}
VB ...
Private IEntityQuery(Of Order) query = _em1.Orders.Where(Function(o) o.OrderID = 10248)
_em1.ExecuteQueryAsync(Of Order)(query, GotOrders, Nothing)
...
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:
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:
When we run the full snippet, the code displays the following results in the Console window:
IdeaBlade DevForce Business Object Persistence
The output line “Press ENTER to continue..” comes from the utility method PromptToContinue(), which executes
synchronously and immedately. Then we see reflected back the OrderID of the retrieved Order; the non-existent
CompanyName of the not-yet-retrieved, related Customer; the CompanyName of the Customer written after its
retrieval by the Customer_PendingEntityResolved callback method; and the display of OrderDetails retrieved,
written by the OrderDetails_PendingEntityListResolved method.
PromptToContinue();
}
These are handy when the logic to be included in the callback isn‟t too involved. VB.NET doesn't support multi-
statement lambda expressions or anonymous methods.
Deferred Retrieval
When does the EntityManager fetch myOrder‟s line items from the data source?
We might have written DevForce to fetch them automatically when it fetched myOrder. But if DevForce were to get
the line items automatically, why stop there? It could get the customer for the order, the sales rep for the order, and
the products for each line item.
Those are just the immediate neighbors. It could get the customer‟s headquarter address, the sales rep‟s address and
manager, and each product‟s manufacturer. If it continued like this, it might fetch most of the database.
Retrieving the entire graph is obviously wasteful and infeasible. How often do we want to know the manager of the
sales rep who booked the order? Clearly we have to prune the object graph. But where do we prune? How can we
know in advance which entities we will need and which we can safely exclude?
IdeaBlade DevForce Business Object Persistence
We cannot know. Fortunately, we don‟t have to know28. We keep it simple. We use an entity query to get the root
entities (such as myOrder). Then we use entity navigation to retrieve neighboring related entities as we need them.
This just-in-time approach is called deferred retrieval (also known as “lazy instantiation”, “lazy loading”, “Just-In-
Time [JIT] data retrieval”, and so on).
28
We don‟t have to know if we can be certain of continuous connection to the data source. If we expect the application to run
offline, we‟ll have to anticipate the related entities we‟ll need and pre-fetch them. We‟ll get to this issue later.
IdeaBlade DevForce Business Object Persistence
Customers who meet a specified condition. Having done so, all of our subsequent queries for entities can be cache-
only and synchronous:
Code Snippet 36. NavigationAsynchronousPreload
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, 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>”.
LoginAsync
LogoutAsync
ExecuteQueryAsync
ExecuteQueryAsync<T>
SaveChangesAsync
ForceIdFixupAsync
RefetchEntitiesAsync
InvokeServerMethodAsync
Asynchronous communication with the BOS is considerably more complicated than synchronous; but alas, it is the
law of the land in Silverlight applications. So, for those many of you who are working in that environment, we are
addressing the topic here, rather than in the Business Object Persistence – Advanced document.
The EntityManager supports a hybrid of the .NET event-based asynchronous pattern31 for these asynchronous
methods. We refer to it as a “hybrid” because corresponding events have not been defined for these methods. So
instead of subscribing to an event to receive notification about the completion status, you can instead pass a method-
specific callback as part of the call. You can identify this hybrid pattern by the OperationNameAsync naming
convention.
See, for example, the code below to submit a query asynchonously.
Asynchronous Queries
You‟ve seen asynchronous queries earlier in this document, but here we revisit them with a slightly more formal
treatment, and in the context of other asynchronous communications with the Business Object Server.
29
Like real cached entities, null entities must belong to a EntityManager and, in fact, are created by a EntityManager
30
This method is inherited from the root business object class, Entity.
31
The standard .NET “Event-based Asynchronous Pattern” is described in described in an article at this URL:
http://msdn2.microsoft.com/en-us/library/wewwczdw(en-US,VS.80).aspx
IdeaBlade DevForce Business Object Persistence
The following code defines an EntityQuery and launches it asynchronously, assigning the result set to a list in the
operation‟s callback method:
Code Snippet 37. BOSCom_AsyncQuery
As you‟ve seen previously, in C#, you have the additional option of passing a lambda expression for the callback
instead of defining a separate method:
Code Snippet 38. BOSCom_AsyncQueryLambda
C#
private void BOSCom_AsyncQueryLambda() {
var query = new EntityQuery<Customer>().Where(c => c.Country == "Denmark");
int token = 2;
_em1.ExecuteQueryAsync<Customer>(
query,
(args) => {
var resultList = args.Result;
Console.WriteLine("Query returned {0} entities", resultList.Count());
},
token);
PromptToContinue();
}
IdeaBlade DevForce Business Object Persistence
You can run multiple ExecuteQueryAsync operations simultaneously. The final parameter, userState, is a unique
object created by the developer to identify the async query. When a query completes, the UserState is returned to the
caller as part of the EntityFetchedEventArgs argument so she can distinguish one query from another. The
UserState object can be as simple as an integer, or it can be an arbitarily complex custom type.
Completed Queries
The EntityFetchedEventArgs parameter passed into an async query‟s callback method contains the following
members:
Property Description
Cancelled True if the query was canceled.
Cancellation of an async operation can be ordered by a call to
EntityManager.CancelAsync(), which takes a UserState object as a parameter.
Such cancellation only succeeds if the order is received in time. Note that
UserState objects must be unique across all async operations, whether queries,
saves, logins, or other.
ChangedEntities An IList containing every entity added to or modified in the EntityManager
cache.
Error An Exception object, of an exception was thrown during the async operation.
IsCompleted True if the operation completed successfully.
IsCompletedSynchronously True if the operation was executed synchronously and completed successfully. A
query, for example, will execute synchronously if DevForce determines that it
can be satisfied entirely from the EntityManager cache.
IdeaBlade DevForce Business Object Persistence
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.
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
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
C# _employeeEntityListManager.ManageList(_telecommuters, false);
_employeeEntityListManager.ManageList(_fieldAgents, false);
VB _employeeEntityListManager.ManageList(_telecommuters, False)
_employeeEntityListManager.ManageList(_fieldAgents, False)
Of course, it only makes sense to do this when the same inclusion criteria apply to each targetted list.
In additions to changes to the cache, changes to a managed list trigger action by the managing EntityListManager.
Thus, any of the follows statements will cause _employeeEntityListManager to examine the current contents of the
cache and add references to all London employees to the _salesReps list:
C# _salesReps.Add(anEmployee);
_salesReps.Remove(anEmployee);
_salesReps.Clear();
VB _salesReps.Add(anEmployee)
IdeaBlade DevForce Business Object Persistence
_salesReps.Remove(anEmployee)
_salesReps.Clear()
In the case of the statement _salesReps.Clear(), you will not end up with an empty list unless you first remove
_salesReps from the list of lists being managed by employeeEntityListManager. Removing an entity that the rule
says should be included also will not result in the entity disappearing from the list. The EntityListManager will just
put it right back! In general, beware of making manual changes (adds or removals) to the set of items contained in a
managed list.
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!
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:
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”.
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);
}
''' <summary>
''' This rule gets the 1996 Orders for the current Customer
''' </summary>
''' <param name="pOrder"></param>
''' <returns></returns>
Private Function FilterOrdersByDate(ByVal pOrder As Order) As Boolean
Return (pOrder.OrderDate.Value.Year = 1996 AndAlso
IdeaBlade DevForce Business Object Persistence
pOrder.Customer.Equals(_currentCustomer))
End Function
Entity Caching
There are at least three good reasons to cache business objects:
1. The connection to the server may break during a session
2. Writing business object changes directly to the data source is impractical and often unwise.
3. In real life applications, the same entities are retrieved repeatedly; it wastes time and resources to bother the
server with redundant requests for the same entities.
Each DevForce EntityManager has its own, private entity cache that:
holds all retrieved and newly created entities;
is searchable by query and object navigation;
tracks cached entity changes, deletions and additions;
insulates the developer from cache mechanics;
enables the developer to control how entities are fetched and merged into the cache;
raises events when entities are fetched, changed, deleted, or added;
permits the developer to manipulate the cache when necessary;
can be persisted to and retrieved from client storage.
IdeaBlade.EntityModel.IEntityBase;
System.ComponentModel.IEditableObject;
System.ComponentModel.INotifyPropertyChanged; and
SystemRuntime.InteropServices.IComparable.
Previously an Entity was a DataRow, and as such resided in a System.Data.DataTable, which in turn resided in a
System.Data.DataSet. Now an Entity lives in an IdeaBlade.EntityModel.EntityGroup which lives within an
EntityGroupCollection. You may find that you rarely need to interact directly with an EntityGroup or
IdeaBlade DevForce Business Object Persistence
EntityGroupCollection; and virtually all of the metadata you will ever need about an entity can be accessed through
the Entity‟s EntityAspect.EntityMetaData property. Public properties and methods of that include the following:
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.
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.
32
Multiple EntityManagers have their place but most applications will need only one. Multiple EMs are covered in
“Advanced Business Object Concepts”.
IdeaBlade DevForce Business Object Persistence
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.
33
If we use the default QueryStrategy; we are just about to discuss QueryStrategy so bear with me.
IdeaBlade DevForce Business Object Persistence
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.
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.
The Fetching event provides the query object. Our handler can examine the object (it implements
IEntityQuery) and choose to let the query through, modify it first, or cancel it. If we cancel the query, the Entity
Manager method returns as if it found nothing34.
The Fetched event fires just before the query method returns. Entities have been fetched and merged into the
cache. The event arguments include the list of entities that came from the data source. There might be none if the
query found nothing or was satisfied entirely from the cache. It could include entities of the target entity type – the
kind we expected returned from the query. It could include entities of other types as is likely if this is a span query
or if the query provoked query inversion35.
As previously discussed, there are corresponding server-side events named ServerFetching and
ServerFetched.
Query Workflow
Putting these points together, we can construct a schematic workflow for normal 36 DevForce entity queries and
entity navigation when the application is connected to the Business Object Server (BOS) running on its own
physical tier.
Component Action
34
If the method returns a scalar entity, it yields the return entity type‟s Null Entity; otherwise, it returns a null entity list.
Beware of canceling an entity navigation list query method
35
Span queries are later in this section. We cover “Query Inversion” in the “Advanced Business Object Concepts”.
36
The workflow is different in a few places when we use a different QueryStrategy. See the “QueryStrategy” topic under
“Advanced Business Object Concepts”.
IdeaBlade DevForce Business Object Persistence
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:
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.
In addition, every EntityManager has a DefaultQueryStrategy that is used whenever you do not explicitly specify the
query strategy you want to use with a particular query. You can also change this default:
IdeaBlade DevForce Business Object Persistence
C# _em1.DefaultQueryStrategy = QueryStrategy.Normal;
VB _em1.DefaultQueryStrategy = QueryStrategy.Normal
Entity navigation (e.g., myEmployee.Orders) is implemented with relation queries governed by the
DefaultQueryStrategy. In addition, any query whose QueryStrategy property has a value of null will be
executed with the DefaultQueryStrategy for the EntityManager underwhich it is run.
The QueryStrategy object has four properties: FetchStrategy, MergeStrategy, InversionMode, and
TransactionSettings. The FetchStrategy controls where DevForce looks for the requested data: in the cache, in the
datasource, or in some combination of the two. The MergeStrategy controls how DevForce resolves conflicts
between the states of objects which, although already in the cache, are also retrieved from an external source. The
InversionMode controls whether DevForce attempts to retrieve objects that are referenced in the query but are not
the target type (e.g., the query “give me all Customers with Orders in the current year” will return references to
Customer objects, but must process Order objects along the way). The TransactionSettings object permits you to
control the TimeOut and IsolationLevel associated with a query, and also whether and how to use the Microsoft
Distributed Transaction Coordinator.
There are five static (Shared in VB) properties in the IdeaBlade.EntityModel.QueryStrategy class that
return the five most common combinations of a FetchStrategy, a MergeStrategy, and an InversionMode. These will
be named and discussed momentarily, but are much easier to understand after examining the available
FetchStrategy, MergeStrategy, and InversionMode options.
Fetch Strategies
Five FetchStrategies are available in DevForce:
Table 3. FetchStrategies
Strategy Action
CacheOnly Apply this query against the cache only, returning references
only to entities already there. Do not consult the data source.
(Note that this query leaves the cache unchanged.)
DataSourceOnly Retrieve matching entries from the datasource into the entity
cache. Return references only to those entities retrieved
from the the data source. A result set returned from a query
using this FetchStrategy would not include locally added
entities that had not yet been persisted to the data source.
DataSourceThenCache First retrieve matching entries from the datasource into the
entity cache. Discard all references to entities retrieved in
this step.
Resubmit the same query against the updated cache. Return
references only to entities matched by this second,
CacheOnly query.
DataSourceAndCache First retrieve matching entries from the datasource into the
entity cache. Retain references to entities retrieved in this
step.
IdeaBlade DevForce Business Object Persistence
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
OverwriteChanges Overwrites the cached entity with data from the data source. Sets the
EntityState of the cached entity to Unchanged.
PreserveChangesUnless Preserves the values in the Current state of the cached entity, if its
OriginalObsolete Original state matches the state retrieved from the datasource.
37
See the discussion on query inversion for more detail.
38
Conflicts are diagnosed by comparing the values in the entity‟s designated Concurrency column.
IdeaBlade DevForce Business Object Persistence
If the state as retrieved from the datasource differs from that found
locally in the Original set of property values, this indicates that the
entity has been changed externally by another user or process. In this
case (with this MergeStrategy), DevForce overwrites the local entity,
setting the values in both its Current and Original states to match that
found in the datasource. DevForce also then sets the EntityState of the
cached instance to Unchanged.
PreserveChangesUpdateOriginal Unconditionally preserves the values in the Current version for the
cached entity; and also updates the values in its Original version to
match the values in the instance retrieved from the datasource. This
has the effect of rendering the local entity savable (upon the next
attempt), when it might otherwise trigger a concurrency exception.
NotApplicable This merge strategy must be used – and may only be used – with the
CacheOnly fetch strategy. No merge action applies because no data is
retrieved from any source outside the cache.
We drill deeper into the topic of merge strategies in the section “MergeStrategy In More Detail” much later in this
chapter. We suggest you defer reading that at least until you‟ve completed this section on Query Strategy – so you
don‟t miss the big picture.
InversionMode
Query inversion applies to queries which:
b) though returning references to instances a single business object type, or a scalar simple type, must process
other types in order to acquire the result.
For example, the query “get me all Customers with Orders in the current year” will return references to Customer
objects, but must first examine many Order objects in order to return the correct set of Customers. The query “give
me the count of Customers located in Idaho” will return an integer, but must examine the Customer collection in the
data source.
Query inversion is the process of retrieving those non-targeted objects that are nonetheless necessary for correct
completion of a query. The most fundamental reason for doing query inversion is so that the query can be applied
against a pool of data that combines unpersisted local data with data that exists in the datasource. This is, after all,
what your end user normally wants: query results based on the state of the data as she has modified it.
The only place that combined pool of data can exist, prior to persisting changes, is the local cache. Therefore the
query must ultimately be applied against the cache; and that operation, if it is to return correct results, requires the
cache to contain all entities that must be examined in the course of satisfying the query. So to satisfy the query “get
me all Customers with Orders in the current year”, the cache must contain not only the Customers to which
references will be returned, but also all extant current-year Orders, so we can know which Customers those are.
A handy side-effect of inverting queries is that the same query, if resubmitted during the same application session,
can be satisfied entirely from the cache, without requiring another trip to the datasource. Another results from the
fact that there is a reasonably good statistical chance that the related objects needed for satisfaction of the query will
also be referenced in other ways by the application. In this very common scenario, the effect of the extra data
retrieved is to improve client-side performance by eliminating the need for separate retrieval of the related objects.
IdeaBlade DevForce Business Object Persistence
Note that the end result of a query inversion process is very similar to that which occurs when the .Include() method
is used in a query. Both processes result in the retrieval and local storage of objects that are related to a set of root
objects that are the primary target of a particular query.
Four InversionModes are available in DevForce for a query:
Table 5. InversionModes
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.
A query that returns a scalar result. This includes all aggregate queries (Count, Sum, Avg, etc.). 39
A query whose return type is a single element. These include queries that call .First(), .Last(), and .Single()
A query whose return type is different from the type contained in the collection first referenced.
” 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
39
Note that this group includes the example mentioned earlier in this discussion: “Give me the count of Customers located in
Idaho.”
IdeaBlade DevForce Business Object Persistence
C# query04.QueryStrategy = QueryStrategy.DataSourceThenCache;
VB query04.QueryStrategy = QueryStrategy.DataSourceThenCache
Custom QueryStrategies
As just noted, only five of the possible combinations of a FetchStrategy and a MergeStrategy are covered by the
named QueryStrategies. What if you want one of the other combinations?
You can create your own QueryStrategy by supplying the fetch and merge strategy enumerations to its
constructor. The result is a new immutable QueryStrategy instance40.
40
Immutable meaning that we can get the component fetch and merge strategies but we cannot reset them.
IdeaBlade DevForce Business Object Persistence
C# QueryStrategy aQueryStrategy =
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 a trip to the data source is found necessary, resolve any conflicts that occur between incoming data
and data already cache by giving the local version priority; and
Perform query inversion as needed; if needed and undoable, revert to a DataSourceOnly FetchStrategy.
Your choice of a non-default strategy can be driven by a variety of things. For example, suppose your application
supports online concert ticket sales. Your sales clerks need absolutely up-to-date information about what seats are
available at the time they make a sale. In that use case, it will be essential to direct your query for available seats
against the data source, so a FetchStrategy of DataSourceOnly might be in order.
In code to handle concurrency conflicts, one might need a QueryStrategy with a MergeStrategy of
PreserveChangesUpdateOriginal to make an entity in conflict savable. (The data source version of the conflicted
entity would only be retrieved and used to partially overwrite the cache version after the concurrency conflict had
been resolved by some predetermined strategy.)
You can and will think of your own reasons to use different combinations of FetchStrategy, MergeStrategy, and
InversionMode. Just ask yourself, for a given data retrieval operation, whether the data in the cache is good enough,
or you need absolutely current data from the data source. Then ask yourself how you want to resolve conflicts
between data already cached and duplicate incoming data. Then consider the process DevForce will use to satisfy
the query and make sure it will have the data it needs to give you a correct result. DevForce gives you the flexibility
to set the behavior exactly as need it.
IdeaBlade DevForce Business Object Persistence
Making a One-Time Change to the QueryStrategy With Which a Given Query Is Run
You may find yourself with an existing IEntityQuery object that you don‟t want to disturb in any way, but which
you would like to run with a different QueryStrategy for a specific, one-time purpose. DevForce provides an
extension method, With(), that permits you to do this. 41
When a call to With() is chained to a query, the result may be either a new query or a reference to the original query.
Normally it will be a new query, but if the content of the With() call is such that the resultant query would be the
same as the original one, a reference to the original query is returned instead of a new query.
If you ever want to be sure that you get a new query, use the Clone() extension method instead of With(). With()
avoids the overhead of a Clone() when a copy is unnecessary.
Code Snippet 42. QueryStrategyWithAndCloning
' The With() call in the right-hand side of the following statement
' specifies a query that is materially different from query0, in
' that it has a different QueryStrategy associated with it.
' Accordingly, the right-hand side of the statement will return
' a new query:
Dim query01 As IEntityQuery(Of Customer) = query00.With(QueryStrategy.CacheOnly)
' Because the content of the With() call in the right-hand side
' of the following statement doesn't result in a modification
' of query0, the right-hand side will return a reference to
' query0 rather than a new query.
Dim query02 As IEntityQuery(Of Customer) = query00.With(QueryStrategy.DataSourceOnly)
' If you want to be certain you get a new query, use Clone()
' rather than With():
41
Our topic here is QueryStrategy, but in fact some overloads of the With() method also (or alternatively) permit you to make a
one-time change to the EntityManager against which the query will be run.
IdeaBlade DevForce Business Object Persistence
Span Queries
A EntityManager query method always returns entities of a single type, the return type identified in the query
object. But what about entities related to the returned entities? When do we get those?
Consider a query for second quarter orders. We display them in a grid with their customer names and order totals.
The Order entities entered the cache when we processed the query. Not so the Customer and the OrderDetail
entities that we need to calculate the order total. The EntityManager gets these entities only when we ask for them
explicitly. Such delayed fetching we called deferred retrieval.
The grid control binding calls an Order property each time it fills a cell. The “Customer” and “Order Total”
columns are bound to two properties that resolve to two relation queries, one for Customer entities and one for
OrderDetail entities. This means the grid control invokes two relation queries for each and every row. There are
three rows showing in the screen shot so there will be six queries, each one requiring a round trip to the data source.
In other words, filling this grid requires six trips to the data source.
Now suppose that we had an excellent quarter and placed a thousand orders. The user clicks the “Customer” column
caption, causing the grid to sort by customer. The sort requires examination of every one of those thousand orders.
Most grids will fire every visible property on every examined row. That could mean two thousand separate trips to
the server: one thousand fetches of customers and one thousand fetches of order details.
The UI will stall for ten uncomfortable seconds and then return to its familiar crisp responsiveness. Subsequent sorts
and scrolling are fast; all of the entities are now in cache so there are no trips to the data source42.
But those ten seconds felt like an eternity. The problem wasn‟t the ten seconds; it‟s that they occurred when the user
thought they should not. She expected the search for orders take some time; maybe not ten seconds but she expected
a pause of some length. On the other hand, she expected the sort to happen immediately. When it didn‟t, she thought
there was something wrong with the application.
Is the sort delay necessary? Of course not!
The program cannot anticipate needing the related data and so it fetches entities inefficiently. We know better. When
we grab the thousand orders, we can fetch their customers and order details at the same time. Not every Customer
in the data source. Not every OrderDetail entity either. We only need the customer and order details that are
related to those thousand second quarter orders. We should get them all at once, not piecemeal as we scroll or sort
the grid.
Span queries to the rescue. We can add span instructions to our query so that the EntityManager gets the related
entities when it gets the orders. A span query instruction describes a path along the root entity graph to a particular
entity type know as the span target.
42
The volume of data is not the issue. We might think that we‟d improve performance if we used a view that summed the
OrderDetails on the server. We‟d get one value per row instead of having to bring down the details and sum them locally.
When we try this, we observe no improvement whatsoever. The delays were due entirely to the round-tripping, not the data
volume nor the summations.
IdeaBlade DevForce Business Object Persistence
Of course, a EntityManager returns references to the root objects when it executes a span query. At the same time it
fetches every span target entity related to any of the returned root entities and puts them in the cache.
We‟ll need two of spans for our example. There is a simple syntax for spanning to the immediate neighbors of the
query‟s result entity type:
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)
Again, span queries don‟t change the list of entities to which references are returned from the query. The caller still
receives the same thousand orders. But before returning the orders, the span query processing fetches the related
entities and merges them into the cache. When the grid cells call upon Order properties to return customers or
calculated order totals, those properties will find the pertinent entities waiting in cache.
The main order query is a little slower because there are more entities retrieved. The user won‟t notice; she expected
the search to take a beat or two. The first sort is instantaneous; she is thrilled.
Performance Details
While spans greatly reduce the number of queries submitted to the database, they do not, of course, eliminate them
altogether. Each span resolves to a separate query and each of these span queries necessitates a separate trip to the
database. Thus, if our we added three spans to an Order query, there would be four queries (one for the Orders, one
for the related type referenced in each of the spans) and four trips to the database. But these four trips -- as our
previous discussion has illustrated – might well replace thousands of trips required in the absence of spans.
In an n-tier deployment using the Business Object Server (BOS), the picture is even rosier. In that configuration, the
client submits the entire request, including spans, in a single transmission to the BOS. It is the BOS that makes the
four trips to the database. When the BOS has a fast, fat pipe to the database - as it should – those four trips are very
quick indeed. The BOS then combines the results from its queries against the database into a single package that it
ships back to the client. There has been only one trip across the “slow” connection between client and server!
IdeaBlade DevForce Business Object Persistence
Note also that the total loads on the EntityServer and database are reduced when each client is making efficient data
requests using spans. Thus, every individual client benefits from the improved efficiency of the other clients.
Performance matters ... but not all time and effort spent optimizing performance returns equal results. We strongly
advise instrumenting your queries during development and testing to identify performance hotspots. Then optimize
where it really matters.
The pool of temporary ids maintained by the developer‟s custom implementation of IIdGenerator is also
saved and restored.
The process preserves pending business object changes – additions, modifications, deletes. When the application
next obtains a server connection, it can synchronize local objects with the central data source. It can refresh local
IdeaBlade DevForce Business Object Persistence
unmodified copies of business objects that have been changed by other users. It can save local pending changes,
relying upon DevForce optimistic concurrency checking to prevent overwriting other users‟ changes.
If the developer expects the application to operate offline, she should prep the cache by retrieving the business
objects the user is likely to need before disconnecting and saving the cache locally. While disconnected, queries and
object navigation can only access objects already in cache.
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.
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.
Once your application in running n-tier, you‟ll see communications with the BOS logged as follows:
IdeaBlade DevForce Business Object Persistence
The activity logged just above resulted from execution of the following method in an app:
The method simply fires off five queries that must hit the server to get their data.
C# TracePublisher.LocalInstance.MakeRemotable();
IdeaBlade DevForce Business Object Persistence
VB TracePublisher.LocalInstance.MakeRemotable()
Once your app has made the above call to MakeRemotable(), it begins functioning as a TracePublisher, doing so on
a default port and default service name that matches the defaults on the Trace Viewer. You can also make it a
publisher on a different port and with a different service name, but then you will need to change the settings on the
Trace Viewer to listen on the specified channel.
C# TracePublisher.LocalInstance.MakeRemotable(9010, "MyClientService");
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.
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:
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.
Here‟s some code for the startup window of a simple WPF app. The code launches the WPF TraceViewer during
initialization, and includes a button click handler that launches a query for some data:
IdeaBlade DevForce Business Object Persistence
C# namespace Wpf01 {
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
SetUpTraceViewer();
}
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.
Here‟s some code for the startup program of a simple WinForm app that launches the WinForms TraceViewer
during initialization:
IdeaBlade DevForce Business Object Persistence
VB
The main method, after launching the TraceViewer, launches _customerForm as the startup form:
IdeaBlade DevForce Business Object Persistence
The handler for the button‟s click event launches a query for some Customers:
VB
When you click the button, you see activity logged in the TraceViewer:
The TraceViewer logs all operations against the Entity Server, so you can use it to see exactly what data loading
operations result from actions performed in the user interface. In this app, additional clicks of the <Load Data>
button result in no further activity against the Entity Server, since the desired Customers, once retrieved into the
cache, can be accessed there thenceforward.
IdeaBlade DevForce Business Object Persistence
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).
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).
43
In a development app with all parts running on a single machine, choose the App.Config file in the AppHelper project.
IdeaBlade DevForce Business Object Persistence
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();
_dataGrid.ItemsSource = _messages;
}
_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:
[... snip]
<ScrollViewer
x:Name="_traceWindowScrollViewer" Grid.Row="1" Margin="0,0,20,0">
<local:TraceWindow />
</ScrollViewer>
[... snip]
</Grid>
</UserControl>
That‟s enough to get it to display client-side trace messages written by DevForce. We can add our own trace
messages as follows...
C# IdeaBlade.Core.TraceFns.WriteLine("Hello world!");
VB IdeaBlade.Core.TraceFns.WriteLine("Hello world!")
IdeaBlade DevForce Business Object Persistence
...resulting in the following output (the TraceWindow control contains the DataGrid):
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.
GUIDs
GUIDs (globally unique identifiers) make great identifiers (aka “ids”) because they are easy to mint, are nearly
certain to be unique, and can be generated locally, independent of any external resource. If we are in complete
control of the database schema design, GUIDs are the way to go.
The MS SQL uniqueidentifier data type is the database analog for a GUID.
When we need a new GUID, we ask .NET to compute one for us, assign it to the prototype‟s identifier data member,
and move on to the next step in object creation.
GUIDs have two disadvantages:
GUID values are long and obscure. Users find them difficult to type correctly and difficult to remember.
At 16 bytes, the GUID is large compared to other data types such as 4-byte integers. Database indexes built
using GUID keys may be relatively slower than indexes using an integer key.
IdeaBlade DevForce Business Object Persistence
In our experience, striving for meaningful identifiers leads to disappointment and failure; we strongly council
against using identifiers with semantic content. If you disagree, you may regard these additional GUID properties as
disadvantageous:
GUID values are random and cannot accept any patterns that may make them more meaningful to users.
There is no way to determine the sequence in which GUID values are generated. They are not suited for
applications that depend on incrementing key values.
If GUIDs work for you, you may skip the next section on custom id generation.
Unfortunately, few of us have this option. We are usually given a database that we cannot change. We‟re not
allowed to replace all table ids and all foreign key columns with 16 byte integer GUIDs. We have to conform to the
existing key schemes which impose both the identifier data types and the manner of their generation.
Custom id generation
Custom id generation almost always requires access to some external resource, some application-specific logic for
deriving new ids, and additional logic to increment the resource.
Suppose our application uses integer keys for all of its tables. The database has a special “NextId” table that holds
the next integer id. To get a new id, a server-side process could quickly lock that table, grab the id, update the table
to hold a new next id, and free the table.
This is just one among thousands of ways applications generate ids. The commonality is the external resource, the
functional equivalent of the NextId table, without which we could not be sure of generating identifiers that are
unique within the application domain.
The developer must write the code that reads the resource, calculates ids, and updates the resource.
If only it were this simple. Remember that we are describing a smart client application in which new object creation
begins on the client machine. The client machine could be disconnected and thus unable to reach a NextId table or
some other external source of permanent ids.
We still want to be able to create new objects while disconnected. We know that we will have to connect to that
external resource to get permanent ids and store the new objects in the database. In the interim, we must finesse the
situation and use locally generated, temporary ids until we can reconnect and replace them with permanent ids.
For example, since our permanent ids are always positive integers, we could use negative integers for temporary ids,
acquiring them by decrementing a client-side counter.
We assign temporary ids (however generated) to the new objects and to the foreign keys of the objects that reference
them. At some point when we‟re sure we‟re connected, we run around to all the locations with temporary ids and
replace them with permanent ids.
Id Fix-up
Just before we save objects back to the database is a good time to attempt this fix-up because (a) we must be
connected to save and (b) we must fix all locally modified objects before saving any of them in case one such object
has a reference to a temporary id.
IdeaBlade DevForce Business Object Persistence
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.
44
At least, there would be no such property generated by the DevForce Object Mapper
IdeaBlade DevForce Business Object Persistence
Sample Id Generator
DevForce ships with source for example id generator classes that you can either use directly or adapt for your
application.
It‟s now easy to see why we prefer GUIDs. We can use .NET‟s free GUID generator while disconnected
because it works locally without resort to an external resource. GUIDs are globally unique so the ids we
create are fine as permanent ids. All of the complexity disappears. The 16-byte cost of GUIDs is usually
worth it. Use GUIDs if you can.
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.
Add()
The Add() method takes a parameter of the type contained by the collection (e.g., an Order).
Code Snippet 44. AddUsingAdd()
anEmployee.Orders.Add(anOrder);
anEmployee.Orders.Add(anOrder)
Invoking Add() adds the supplied item to the collection. If the relation between the parent and child types is 1-to-
many and the supplied item is currently associated with a different parent, then Add() simultaneously removes it
from the corresponding collection of the other parent. 45
45
The equivalent result on table rows in a relational database is that the child entity‟s foreign key value is changed.
IdeaBlade DevForce Business Object Persistence
Note that, in the above snippet, we did not need to set the SalesRep property of the new Order to the Employee
whom we wanted to become its parent:
VB ' anOrder.SalesRep = anEmployee '' don't need this; Add() will handle it
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.
46
Speaking again of the equivalent result on table rows in a relational database, the child entity‟s foreign key value
is set to null.
47
See the appendix “Many-to-Many Associations in the Entity Framework” in the Object Mapping chapter for more information.
48
Note that those objects are not exposed in the conceptual model, and are never manipulated directly by you.
IdeaBlade DevForce Business Object Persistence
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.
EntityState of an Object
Unmodified entities are never saved. Attempts to save them are ignored.
The application can determine if a particular object is new, modified, marked for deletion, or unmodified by
examining its EntityState property which returns one of the corresponding EntityRowState enumerations.
The application can also query the cache for all entities that are in one particular EntityState or specific
combination of EntityStates and submit them together for save.
Undo
Modified business objects don‟t have to be saved. The application can undo changes made to a single object or a list
of objects in the cache.
This is a single level undo. Undoing a pre-existing object, whether changed or marked for deletion, restores it to its
state when last retrieved from the data source 49; its EntityState becomes “unmodified.” Undoing a newly created
object deletes it immediately and removes it from the cache.
There is no undo of an undo.
Multi-level Undo
The EntityManager provides “Checkpoint” methods that facilitate implementation of applications that need multi-
level undo. The utility of “checkpointing” is most apparent in the UI so we cover it in the WinForm User Interfaces
chapter in the topic “Multi-Level Undo with Checkpoints”.
Validation
The wise developer will validate business objects before saving them.
Many developers perform validity checks in the presentation layer. Some checks in the UI make sense especially
when they provide crisp and immediate user feedback.
But good design keeps most validation logic out of the presentation layer and delegates it to the business object.
Here are four good reasons:
49
Technically, undoing a modified entity sets the “current” version of the entity to its “original” version. Entity versions are
covered in “Advanced Business Object Concepts”.
IdeaBlade DevForce Business Object Persistence
As the application evolves there are likely to be multiple screens – even multiple UIs – updating the
same business object. There is high risk that they will perform validation differently and omit essential
checks if each handles its own validation.
The object may be changed by a batch program or by a web service. We need to perform the same
validations in these modes as we do in a graphical interface.
Cross-field and cross-record checks in the UI can create deadlocks and recursion problems. It‟s easier to
apply rules such as “the birth date comes before the hire date” and “orders weighing more than 100
pounds must be shipped by ground” after the user presses a button rather than try to enforce them while
the user is typing.
It‟s easier to break up or combine forms in an interface if you don‟t also have to juggle the validation
code to match.
DevForce offers extensive facilities for defining and executing validation logic. See the chapter, “Validation
Through Verification”.
Temporary Id Fix-up
Initiation of any save operation causes the EntityManager to attempt to replace temporary ids with permanent ids.
Subsequent success, failure, or cancellation is immaterial. The act of saving launches the fix-up process. The fix-up
process was covered above, in the section “Id Fix-up”. Be sure you understand the fix-up process as detailed in that
section.
Fetching Modify the query being submitted, or refuse the request for data.
Saving Modify the object submitted for saving, or refuse the request to
perform inserts and/or updates.
Saved Modify the saved object (which might be different from the object
submitted for saving by virtue of triggers that were fired on the back
end to modify the latter after it was saved).
The EntityManager raises a Fetching event shortly after the application initiates a data retrieval operation. It
raises a Fetched event if any entities are retrieved successfully. We can add our own event handlers to these events.
The Fetching event provides the handler with a copy of the query object that the caller proposes to submit. The
event handler can scrutinize the query object, modifying it or rejecting the query entirely if security or other
considerations make that the appropriate response.
IdeaBlade DevForce Business Object Persistence
The EntityManager raises the Fetched event if any entity is retrieved. The handler receives a list of the entities
that were retrieved.
The EntityManager raises a Saving event shortly after the application initiates a save. It raises a Saved event if
any entities are saved successfully. We can add our own event handlers to these events.
The Saving event provides the handler with a list of entities that the caller proposes to save. It will calculate that list
if the method parameters do not prescribe the list50. The event handler can scrutinize the list, invoke validation
methods on selected entities, clean up others (e.g., clear meaningless error conditions), add additional entities to the
list, and even exclude entities from the list. Lastly, it can cancel the save.
The EntityManager raises the saved event if any entity is saved. The handler receives a list of the entities that
were saved successfully.
In transactional saves, either every entity in the save list is saved or none of them are. In DevForce, saves are always
transactional, even across disparate back-end data sources.
ServerFetching Modify the query being submitted, or refuse the request for data.
ServerSaving Modify the object submitted for saving, or refuse the request to
perform inserts and/or updates.
ServerSaved Modify the saved object (which might be different from the object
submitted for saving by virtue of triggers that were fired on the back
end to modify the latter after it was saved).
These events provide the developer with the opportunity to do perform server-side, before-the-fact and after-the-fact
operations on both queries and saves. The EntityManager, which resides client side, provides corresponding client
side events: Fetching, Fetched, Saving, and Saved. The developer thus has complete flexibility to perform
centralized processing on data retrievals and updates, client-side or server-side, as her use case dictates.
For those familiar with DevForce Classic, the EntityServer.ServerFetching event replaces the DevForce Classic
PersistenceServer‟s QuerySecurityCheck event. Similarly, EntityServer.ServerSaving replaces
PersistenceServer.SaveSecurityCheck.
ServerFetching will have access both to the submitted query object and to an IPrincipal representing the
authenticated user who made the request. ServerFetched, ServingSaving, and ServerSaved will also have
access to that same IPrincipal, but instead of a query object they will have access to the full collection of DevForce
entities being retrieved or updated. Thus, the ServerFetched, ServingSaving, and ServerSaved event
handlers will make use of the copy of the Domainmodel assembly that has been deployed server-side.
50
SaveChanges() with no arguments, for example, is a blanket request to save every changed entity in cache.
IdeaBlade DevForce Business Object Persistence
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:
Here‟s a post-build event that ensures that the Server assembly will be deployed to the executables folder in a single-
machine development environment:
IdeaBlade DevForce Business Object Persistence
Recall that modified objects include additions, updates, and deletes. Deleted records are actually marked
for delete and must be “saved” to be deleted from the data source.
EntityManager saves are transactional by default. When the developer saves more than one entity at a time,
DevForce processes them together as a single unit of work. Either every save succeeds, or they are all rolled back.
Behind the scenes, DevForce causes the necessary INSERT, UPDATE, and DELETE statements to be wrapped
within “Begin Transaction” and “Commit Transaction” or “Rollback Transaction” statements. If all succeed the
transaction is committed. If any fail, the data source is restored to its pre-transaction condition51.
The application relies upon the data source manager to provide two key benefits throughout the transaction:
Consistency - simultaneous queries and change requests cannot collide with each other, and users must never see or
operate on data that is in mid-change. In a multi-user environment the data source manager must prevent
simultaneous queries and data modification requests from interfering with each other. This is important because if
the data being processed by a query can be changed by another user's update, the results of the query may be
ambiguous.
Recovery - in case of system failure, data source recovery is complete and automatic.
51
We cover save failures in topic coming up soon.
IdeaBlade DevForce Business Object Persistence
SQL defines different degrees of consistency enforcement called “isolation levels”. Each database vendor
has a different default isolation level and a proprietary syntax to change it. The developer is responsible for
setting the database isolation level and all other global database behavior options. Such settings may be
made in the database itself or with proprietary information embedded in the connection string. Consult the
database vendor‟s documentation.
Distributed Transactions
DevForce can provide transactional integrity when saving entities to two or more data sources. These data sources
can be of different types from different vendors. Their data source managers must support the X/Open XA
specification for Distributed Transactions52.
The developer instructs DevForce to use the .NET Enterprise Services (AKA, COM+) Distributed Transaction
Coordinator (DTC) to handle transaction management.
DTC performs a two phase commit. In the first “prepare” phase all parties to the transaction signal their readiness to
commit their parts of the transaction. In so agreeing they must guarantee that they can complete their tasks even
after a crash.
If any participant does not agree, all parties roll back the transactions they have underway.
If all participants agree, the transaction moves into the second, “commit” phase in which the parties actually commit
their changes.
If the transaction is successful, the entities are re-queried.
52
At this writing, databases are the only DevForce supported data sources that support the X/Open XA protocol.
IdeaBlade DevForce Business Object Persistence
Do wrap every SaveChanges in a Try/Catch and analyze the exception when thrown.
Here‟s a code fragment showing a Save method that matches our recommendation:
Code Snippet 45. WhenSaveFails()
The serious failure interpretation and recovery work is in the ProcessSaveFailure method which is custom code
that we write. The information we need is in the EntityManagerSaveException instance passed as a parameter to the
method.
SaveChanges() Exceptions
The EntityManager raises a EntityManagerSaveException if the save is canceled (e.g., you cancel it in your
Saving event handler) or if there is any kind of exception.
The EntityServerError handler gets the first crack at the exception. If there is no handler or it doesn‟t handle the
exception, the PM throws it again, now in the context of the SaveChanges() call.
We recommend that you do not handle save exceptions in the EntityServerError; leave that to the code near
your SaveChanges() call that catches and interprets save failures.
You‟ll find examples of this recommendation in the Funhouse in the ApplicationController and
EmployeePageController classes.
EntityManagerSaveException
The EntityManagerSaveException inherits from EntityServerException, supplementing that base class
with information pertaining to the save.
IdeaBlade DevForce Business Object Persistence
That information includes an instance of SaveResult such as would have been returned from SaveChanges().
We‟ll discuss that in a moment. First we‟ll get a rough idea of what went wrong by looking at the exception‟s
Failure Type.
FailureType Description
Connection The Entity Manager could not reach the data source. There might be a network
connection problem or the data source itself could be down.
Data The data source threw an exception such as a referential integrity violation.
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.
53
The “data source entity” is the term of convenience we use to describe the data in the data source that map to a corresponding
entity in cache. The data source entity may be a single row in a database table as when an Employee cached entity maps to a
row in an Employee table. Alternatively, the data may be scattered in many places in some other kind of data source. We
have no clue as to the actual location of data behind a Web service entity.
IdeaBlade DevForce Business Object Persistence
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.
54
You can, of course, safely omit the primary key.
IdeaBlade DevForce Business Object Persistence
timestamp, or any other type and methodology that guarantees that the value will change in a non-cyclical way. As
you have seen, DevForce makes it easy for you to make a column auto-updating.
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()
' the value from the datasource when most recently retrieved
Employee.FirstNameEntityProperty.GetValue(anEmployee, EntityVersion.Original)
The effect is as if we had just read the entity from the datasource and applied the user‟s changes to it.
55
We discuss merge strategies in “Advanced Business Object Concepts”.
56
We cover entity versions in “Advanced Business Object Concepts”.
IdeaBlade DevForce Business Object Persistence
If we ask the persistence layer to save it now, the datasource will “think” that we modified the most recently saved
copy of the entity and welcome the changed record.
This option is much like “last one wins” concurrency with a crucial difference: it was no accident. We detected the
concurrency collision and forced the issue in accordance with approved business rules.
The 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.
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
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.
Association Types
Associations come in a variety of strengths:
Type Description
Association A simple association is typically read as a “Has a” relationship. An Address has a State or a Part
has a Color.
The two ends have independent lifetimes. A change to the city or the name of the part does not
alter the state or the color.57
Aggregation An aggregation implies a stronger, “Owns a” relationship. A Company owns its employees.
The two ends still have independent lifetimes. There is still a law against slavery and the
employee may transfer to another company. Yet the bond between Company and Employee is
stronger than between Part and Color. There are ramifications to the making and breaking of
ties.
Composition A composition is a “whole / part” relationship in which the whole is said to “consist of” or “be
made up of” the parts. An Order is substantially made up of its detailed items.
This is the strongest bond. The lifetimes of the two ends are closely tied. If the order disappears,
its details disappear with it. Adding, changing, or deleting details alters the parent order.
DevForce itself has no mechanism for distinguishing among these association types. In fact, DevForce treats its
relations as the simplest association. It makes no assumption about the consequences for related entities of any
alterations to either parent or child.
It is not clear that there is a meaningful programmatic distinction between Association and Aggregation. There will
be more business rules surrounding an Aggregation but business rules always require custom coding so the
difference is one of degree rather than of kind.
The relevant fact in this context is that parent and child may be modified independently. Yes, we must adjust the
child if we delete the parent. There may be constraints and consequences to joining and separating parent and child.
There can be side-effects of altering parent or child data unrelated to their bond. But, in general, we don‟t require a
modification in one to effect a modification of the other.
Compositions
There are systems that explicitly support the Composition distinction. If you mark a relationship as a composition,
the system will implement it differently. The parts (children) in a composition will be contained by the whole
(parent) and they may only be accessed through the parent.
If you marked an Order‟s OrderDetails property as a composition, the only way to obtain details would be through
this property. OrderDetails fetched through any other mechanism would be different objects than the conceptually
same entities fetched with the OrderDetails property.
We think that is a rare and extreme position which is more trouble than it is worth. The developer can program to it
when it occurs but DevForce does not encourage the practice with any means of its own. No mechanism is provided
to mark the OrderDetails navigation property as a “Composition”.
But this is not to diminish the importance of the Composition bond. In many applications, we should consider the
Order modified if we add, change, or delete one of its OrderDetail entities. If we delete the Order, we almost
certainly intend to delete its details as well.
57
They may become incompatible – as when the change to city moves the address to a different state or the part turns out to be
colorless – but compatibility is a matter for business rules unrelated to the fundamental nature of the association.
IdeaBlade DevForce Business Object Persistence
This is precisely the behavior sought by systems with native support for composition. But we can achieve the same
effect in DevForce. It is not hard work, although it requires some care. The reward is flexibility.
Each application has its own requirements.We can offer only a brief outline of the main points here.
Our application “save” operation concentrates on the root entity (or enties) of the dependency graph.
We implement a Saving handler to invoke composition business rules of the entities.
We add the composition business rules to the business object, wrapped in a method the Saving handler
can call.
We provide for intelligent concurrency resolution to detect and manage the collision of our changes
with changes by other users.
C# anOrderDetail.UnitPrice *= 1.1M;
Order parentOrder = anOrderDetail.Order;
if (parentOrder.EntityAspect.EntityState == EntityState.Unchanged) {
parentOrder.EntityAspect.SetModified();
}
VB
IdeaBlade DevForce Business Object Persistence
anOrderDetail.UnitPrice *= 1.1D
Dim parentOrder As Order = anOrderDetail.Order
If parentOrder.EntityAspect.EntityState = EntityState.Unchanged Then
parentOrder.EntityAspect.SetModified()
End If
Concurrency Violations
We always use transactional saves. We‟ve taken steps to ensure that all members of the “dependency graph” – the
order and all of its details, for example, - are part of the same save list and are slated for persistence as a single
transaction.
When DevForce persistence layer detects a concurrency violation, it terminates the transaction and returns the
offending entity as we learned earlier. Chances are there will be more than one entity in the transaction that is in
potential concurrency conflict with its corresponding object in the data source.
The end user will be most unhappy if we walk her through each entity one by one. We should resolve the
concurrency conflicts of all entities in the dependency graph in a single shot.
While the exact details will be application specific, they will be some variation on the techniques you learned for
resolving conflicts of individual entities.
int employeeID = 1;
// Add span(s).
List<EntitySpan> spans = new List<EntitySpan>();
spans.Add(aSpan);
' 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()
spans.Add(aSpan)
DisplayCacheContents(em2)
PromptToContinue()
End Sub
Component Action
Client Tier – Application The client application adds, modifies and deletes any number of business objects
IdeaBlade DevForce Business Object Persistence
Client – EntityManager Makes a save list of the new, modified, and deleted entities in cache.
Fires the Saving event. Assume that application listener okays the save.
Connects to the data source and authenticates the user. Assume success.
If there are any temporary ids, the PM sends them to the BOS for fix-up.
Middle Tier – Business Builds map of data source-generated ids (e.g., for Identity columns). Calls method
Object Server on instance of developer‟s id generation class with remaining temporary. This
method returns a map of temporary-to-permanent ids which the BOS returns to the
client tier.
Client –EntityManager Uses the temp-to-perm id map to replace all temporary ids.
Transmits the save list to the BOS.
Middle Tier – Business First the Saving event. This can be used to perform security checks on each entity
Object Server in the save list. If a security check fails, an exception can be thrown back to the
EntityManager (or any other desired action taken.) Workflow ends.
Otherwise…
Constructs a batch of insert, update, and delete operations, adjusted for optimistic
concurrency checking as required.
Arranges them by type per the prescribed PersistenceOrder.
Middle Tier – Business If the data source is a relational If the data source is a web service:
Object Server database:
Converts the requests to the approprate
Forwards them to the Entity Framework web service calls and submits them to
for execution. the web service.
Data Tier - Data Source Performs the persistence operations. If there are no failures, it commits them; if
there is a single failure, it rolls them all back.
Middle Tier – Business If the transaction failed, returns to the EntityManager the identity of the culprit
Object Server entity and the exception raised by the data source. The EntityManager stores this
information in the SaveResult and returns to the client application. Workflow ends.
Otherwise…
The transaction succeeded. The BOS re-queries the database(s) for all of the
inserted and modified entities that are sourced in databases, thus capturing the
effects of triggers that fired during save.
Converts the (potentially) revised data into entities and sends them to the client side
EntityManager.
The server‟s local copy of the entities go out of scope and the garbage collector
reclaims them. This enables the object server to stay stateless.
Client Tier – Replaces cashed entities with updates from BOS. They are marked “unchanged”
EntityManager because they are now current.
Raises the Saved event with list of saved inserted and modified entities.
Code
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:
CacheStateManager also includes a method, GetCacheState(), which returns the state of the cache as a serializable
in-memory object:
IdeaBlade DevForce Business Object Persistence
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.
System.Runtime.Serialization.DataContractSerializer, or the
System.Runtime.Serialization.NetDataContractSerializer
is supported.
One of the big issues with XML Serialization when serializing object graphs (objects that are connected to other
objects, ad-infinitum) has to do with the depth of the object graph that should be serialized. Without some
mechanism to control the depth of the graph, the serialization of a single entity might result in hundreds or even
thousands of related entities being serialized.
DevForce controls this by only serializing entities that are present within an EntityManager‟s cache at the inception
of serialization and are navigable from the directly serialized entities. (Think of this as all entities that are available
via a CacheOnly query) This allows fine-grained control over what will be serialized. Any relation properties that
would return an entity or entities that are not in the cache will instead serialize the property value either as a null
entity (for scalar properties), or as an empty collection (for collection properties).
The examples serializes two employees along with all related Orders and their line items (OrderlDetails):
Code Snippet 49. SerializeBusObjects()
IdeaBlade DevForce Business Object Persistence
if (serializerName == "NetDataContactSerializer") {
SerializeWithNetDataContractSerializer(employees, aXmlDictionaryWriter);
}
else {
SerializeWithDataContractSerializer(employees, aXmlDictionaryWriter);
}
aXmlDictionaryWriter.Flush();
aMemoryStream.Position = 0;
string result = StreamFns.ToString(aMemoryStream);
}
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)
aXmlDictionaryWriter.Flush()
aMemoryStream.Position = 0
Dim result As String = StreamFns.ToString(aMemoryStream)
Console.WriteLine("Two employees, serialized using the {0}..." & vbLf, serializerName)
Dim abbreviatedResult As String = result.Substring(0, 500) & vbLf & vbLf &
"...[snip]..." & vbLf & vbLf & result.Substring(result.Length - 201)
Console.WriteLine(abbreviatedResult)
PromptToContinue()
End Sub
IdeaBlade DevForce Business Object Persistence
C# EntityMetadata employeeEntityMetaData =
EntityMetadataStore.Default.GetEntityMetadata(typeof(DomainModel.Employee));
VB
Method CreateEntity() Creates a new entity of the type described by this metadata item.
Property DataSourceKeyName Gets the name of the Data Source Key associated with this type.
IdeaBlade DevForce Business Object Persistence - Advanced
Property DefaultEntitySetName Gets the default EntitySetName for entities of this type.
Property KeyProperties Returns a collection of EntityProperties that are keys 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.
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.
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.
Recall that in the Entity Framework – in contrast to the behavior DevForce -- all related entities must be retrieved by
explicit command. When such command is given, EF always returns the related entities. But for parent entities that
were retrieved using stored procedures, it necessarily uses a different (and less performant) process to get the related
entities than for entities retrieved using ordinary queries. That is made necessary by the lack of foreign key values
on the parent entities.
58
There is an advanced technique for applying a stored procedure query to the cache that we cover briefly in “Advanced
Business Object Concepts.”
IdeaBlade DevForce Business Object Persistence - Advanced
DevForce, in contrast to the EF, retrieves related entities automatically; all you need do is to make reference to
them. However, in the case of entities retrieved via stored procedure queries, we had to make a tough call. One
choice was to retrieve foreign key values automatically during any stored procedure query. That would produce the
simplest and most intuitive behavior on the client: for all entities, retrieval of related entities referenced through
navigation properties would be automatic.
But of course there was a problem: each foreign key requires an additional round trip to the database from the object
server; and there is, of course, a performance price for this.
We elected to make the default the more performant choice: unless you ask for them explicitly, we do not retrieve the
foreign key values during stored procedure queries. In consequence, by default, references to navigation properties
on such entities will return Null Entities.
If you know you will need the related entities for entities retrieved using a stored procedure proc, you can get them
via the ShouldLoadEntityRefs property on the StoredProcQuery. If you set this property to true – the default is false
-- all foreign key properties on the entity are looked up during the initial query, and references to related entities will
return the proper entities.
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:
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:
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 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:
Having done all of that in your Entity Model, you can now use the resultant method as shown following two
examples:
[TestMethod]
public void StoredProcQuery() {
DateTime dt1 = DateTime.Parse("1/1/1990");
DateTime dt2 = DateTime.Parse("1/1/2000");
var results = _em1.GetSalesByYear(dt1, dt2);
}
[TestMethod]
public void StoredProcQuery2() {
DateTime dt1 = DateTime.Parse("1/1/1995");
DateTime dt2 = DateTime.Parse("12/31/1996");
var results = _em1.GetSalesByYear(dt1, dt2).Where(s => s.Subtotal > 2500);
}
IdeaBlade DevForce Business Object Persistence - Advanced
VB
The method is simply called on the EntityManager with appropriate parameters. It returns an
IEnumerable<SalesByYear>, which can be subjected to qualifying filters as you see in the second example above.
Below is the Generated code in the domain model designer code file for the GetSalesByYear() method:
/// <summary>
/// Constructs and returns the <see
cref="T:IdeaBlade.EntityModel.StoredProcQuery"/>
/// associated with the given stored procedure.
/// </summary>
public StoredProcQuery GetSalesByYearQuery(
Nullable<DateTime> Beginning_Date, Nullable<DateTime> Ending_Date) {
QueryParameter Beginning_DateParameter;
if (Beginning_Date.HasValue) {
Beginning_DateParameter = new QueryParameter("Beginning_Date", Beginning_Date);
} else {
Beginning_DateParameter = new QueryParameter("Beginning_Date",
typeof(DateTime));
}
QueryParameter Ending_DateParameter;
if (Ending_Date.HasValue) {
Ending_DateParameter = new QueryParameter("Ending_Date", Ending_Date);
} else {
Ending_DateParameter = new QueryParameter("Ending_Date", typeof(DateTime));
}
StoredProcQuery query =
new StoredProcQuery(typeof(IdeaBladeTest1Model.SalesbyYear),
"GetSalesByYear",
Beginning_DateParameter,
Ending_DateParameter);
return query;
}
#endregion GetSalesByYear StoredProcQuery
VB
For the record, here‟s an alternative way to invoke your stored procedure:
IdeaBlade DevForce Business Object Persistence - Advanced
C# [TestMethod]
public void StoredProcQuery3() {
DateTime dt1 = DateTime.Parse("1/1/1996");
DateTime dt2 = DateTime.Parse("12/31/1998");
StoredProcQuery query = new StoredProcQuery(typeof(SalesbyYear));
VB
Table 10. Who writes the navigation property involving a sproc entity.
59
In fact you can‟t do this within the Object Mapper for reasons we are now discussing.
IdeaBlade DevForce Business Object Persistence - Advanced
Forced Re-fetch
There are a number of methods that help us re-fetch specific entities from their data sources. Among them are
EntityList.ForceRefetch and EntityManger.RefetchEntities<T>. They assume the
OverwriteChanges merge strategy but we can give them any of the other merge strategies.
OverwriteChanges replaces the cached entities, overwriting our pending changes. We often want to (a) keep
pending changes but (b) refresh copies of unmodified entities.
The PreserveChanges… strategies can help us achieve our purpose.
Strategy Description
PreserveChanges Replace unchanged entities but keep changed entities as they are.
PreserveChangesUnless Replace unchanged entities and changed entities that are obsolete (i.e., that
OriginalObsolete
would fail an optimistic concurrency check if saved now).
PreserveChangesUpdateOriginal Replace unchanged entities. Keep changed entities and make them current
if they are obsolete by updating their original versions.
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.
Query Cache
DevForce caches queries to improve performance 61. Consider a query for employees with FirstName = “Nancy”.
The QueryStrategy is Normal which means the fetch strategy is CacheThenDataSource.
When we execute this query in an empty EntityManager, there will be a trip across the network to fetch the
entities from the data source. We get back “Nancy Davolio” and “Nancy Sinatra”. If we execute the query again, the
EntityManager satisfies the query from the entity cache and returns the same result; it does not seek data from the
data source.
During the first run the EntityManager stored the query in its Query Cache62. The second time it found the query
in the Query Cache and thus knew it could use apply the cache to the query instead.
60
This analysis applies to both entity query and entity navigation.
61
This analysis applies to both entity queries and entity navigation. Both use CacheFirstThenDataSource fetch strategy
by default.
IdeaBlade DevForce Business Object Persistence - Advanced
If we change “Nancy” to “Sue” and run the query again, we get back just “Nancy Sinatra”. If we change “Sally
Wilson” to “Nancy Wilson” and run it again, we‟ll get the principals of a strange duet. So far, everything is working
fine.
Meanwhile, another user saves “Nancy Ajram” to the data source. We run our query again and … we still have just a
duet. The EntityManager didn‟t go to the data source so it doesn‟t find the Lebanese pop star.
Such behavior may be just fine for this application. If it is not, the developer has choices. She can:
use a QueryStrategy with a different fetch strategy that looks at the database first.
clear the query cache explicitly by calling EntityManager.ClearQueryCache
clear the query cache implicitly by removing any entity from the entity cache
62
The PersistenceManager stores the query in the query cache when (a) the query is successful and (b) it searched the data
source (not just the cache).
63
This is not a rare scenario.
IdeaBlade DevForce Business Object Persistence - Advanced
Fortunately, there is now a RemoveEntities signature that can remove entities without clearing the query cache. In
the full knowledge of the risk involved, we can call
EntityManager.RemoveEntities(entitieToRemove, false)
The “false” parameter tells the PM that is should not clear the query cache.
Remember: removing an entity and deleting it are different operations. Removing it from the cache erases
it from client memory; it says nothing about whether or not the entity should be deleted from its permanent
home in remote storage. “Delete”, on the other hand, is a command to expunge the entity from permanent
storage. The “deleted” entity stays in cache until the program can erase it from permanent storage.
VB
Both of the following statements get the current value for the same property:
C# reqdDate =
Order.RequiredDateEntityProperty.GetValue(anOrder, EntityVersion.Current);
reqdDate = anOrder.RequiredDate; // same as above (but simpler!)
VB
IdeaBlade DevForce Business Object Persistence - Advanced
Again, DevForce and the Entity Framework determine if our cached entity is current or obsolete based on the
original version of the property value.
N OW OW OW OW Y
PreserveChangesUpdateOriginal Y or N NC NC NC NC Y
NC = No change; preserve the current version values of the cached entity
OW = Overwrite the cached entity‟s current version values with data from the data source entity
Post Current = „Y‟ means the cached entity is “current” relative to the data source after the merge.
There are important artifacts not immediately observable from this table.
The entity‟s EntityState may change after the merge. It will be marked Unmodified after merge with
OverwriteChanges. It will be marked Unmodified after merge with
PreserveChangesUnlessOriginalObsolete if the entity is obsolete.
Note that deleted and detached entities are resurrected in both cases.
64
The possible values are Added, Deleted, Detached, Modified, and Unchanged. See “Data Row State” in the
glossary.
IdeaBlade DevForce Business Object Persistence - Advanced
An added cached entity must be deemed “obsolete” if it already exists in the data source 65. We will not be able to
insert that entity into the data source; we‟ll have to update the data source instead.
The PreserveChangesUpdateOriginal strategy enables us to force our changes into the data source even if the
entity is obsolete. An added entity merged with PreserveChangesUpdateOriginal will be marked Modified
so that DevForce knows to update the data source when saving it.
These effects are summarized in the following table:
Table 13. EntityState after merge.
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.
65
The entity exists in the data source if the query returns an object with a matching primary key. If we think we created
Employee with Id=3 and we fetch one with Id=3, someone beat us to it and used up that Id value. Our entity is obsolete.
IdeaBlade DevForce Business Object Persistence - Advanced
If the query tests for anything other than the primary key, we can draw no conclusions from the fact that a cached
entity was not found in the database. For what does it mean if we have an employee named “Sue” in cache and we
don‟t find her in the data source? Perhaps someone deleted her from the data source. Maybe someone merely
renamed her. Maybe we renamed her. The combinations are too many to ponder.
On the other hand, if we query for Employee with Id = 3 and we don‟t find that employee in the data source, we can
be confident of a simple interpretation66. A business object must have unique identity so if it isn‟t there, either it was
never there or it has been deleted. What happens next depends upon the EntityState of the cached entity and the
merge strategy.
DevForce recovers gracefully when it attempts to save an entity marked for deletion and it can‟t find the
data source entity to delete so the merge can leave this cached entity alone. It can also skip over the
detached entities.
PreserveChanges forbids merge effects on changed entities. The entity stays put in the cache.
OverwriteChanges takes the data source as gospel. If the cached entity‟s EntityState is Modified,
there should be an existing data source entity. There is not, so DevForce assumes the data source entity has
been deleted and the cache should catch up with this reality. It removes 67 the entity from the cache.
On the other hand, if the cached entity is new (Added), we don‟t expect it to be in the data source. The
entity remains “as is” in the cache, a candidate for insertion into the data source.
PreserveChangesUnlessOriginalObsolete behaves just like OverwriteChanges.
PreserveChangesUpdateOriginal strives to position the entity for a successful save. It must intervene
to enable data source insertion of a modified entity by changing its EntityState to Added68.
In sum:
Table 15. Merge strategy consequences for a changed cached entity that does not exist in the data source.
DataSourceOnly Subtleties
We may get a nasty surprise if we use a DataSourceOnly or DataSourceThenCache query with other than the
OverwriteChanges merge strategy. Consider the following queries using the PreserveChanges merge strategy.
Suppose we hold the “Nancy” employee in cache. We change her name to “Sue” and then search the database for all
Employees with first names beginning with „S‟. We will not get “Sue” because she is still “Nancy” in the database.
Suppose we search again but this time we search for first names beginning with „N‟. This time we get “Sue”. That
will confuse the end user but it is technically correct because the “Sue” in cache is still “Nancy” in the database 69.
66
DevForce confirms that the primary key has not changed. While it is good practice to use immutable keys, it is not always
so. If the primary key has been changed, DevForce leaves the cached entity alone.
67
Removal from the cache is just that. The entity disappears from cache and will not factor in a save. It does not mean “delete”
which requires DevForce to try to delete the entity from the data source. It is an action neutral to the data source..
68
An update would fail because there is no data source entity to update.
69
DataSourceThenCache will produce the same anomaly for the same reason: the database query picks up the object in
the database as “Nancy” but preserves the modification in cache which shows her as “Sue”.
IdeaBlade DevForce Business Object Persistence - Advanced
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:
The easiest way to get a stub entity is to "new" it up, set some of its properties, give it an EntityKey, and dump it
in our testManager. When we're done it should appear there as an unchanged entity ... as if you had read it from the
datastore.
The catch lies in the answer to this question: "How do I add the entity to the manager?"
In the absence of AttachEntity() method, you would have to use EntityManager.AddEntity(). But after AddEntity,
the EntityState of the entity is always "Added". You want a state of "Unchanged" so you have to remember to call
AcceptChanges (which changes the state to "Unchanged").
That's not too hard. Unfortunately, it gets messy if the key of the entity is auto-generated (e.g., mapped to a table
whose id field is auto-increment) because DevForce automatically replaces your key with a temporary one as part of
its auto-id-generation behavior.
We could explain how to work around this, but what was really needed was a simple way to simulate the result of
retrieving an entity. That's why we created the AttachEntity() method.
Here's the XML documentation for AttachEntity:
Adds a detached entity to this EntityManager in an Unmodified state. Throws an exception if an
entity with the same key already exists in the manager of if the specified entity is not in a detached
state.
Let us elaborate here and compare it to some similar methods by calling out some facts about the
following code fragment:
C# theEntityManager.AttachEntity(object theEntity)
theEntity‟s EntityKey (“the key”) must be preset prior to the attach operation (which will not touch the
key).
An exception is thrown if an entity with that key is already in the cache.
After attach, theEntity is in an “Unchanged” EntityState (“the state”).
IdeaBlade DevForce Business Object Persistence - Advanced
theEntity is presumed to exist in the persistent store; a subsequent change and save will translate to an
update statement.
After a successful attach, a reference to theEntity is a reference to the entity with that key in the
manager‟s EntityCache. Contrast this with the effect of anEntityManager.Imports(new [] {anEntity})” as
discussed below.
theEntity must be in the “Detached” state prior to the operation.
An exception is thrown if theEntity is other than in “Detached” state prior to the operation.
After attach, related entities are implicitly associated with theEntity automatically; for example, if
anOrder with Id==22 is attached and there are OrderDetails with parent OrderId==22, then after the
attach, anOrder.OrderDetails returns these details and any one of them will return „anOrder‟ in response
to anOrderDetail.Order.
The sequence of attachments is not important; OrderDetails may be added prior to the parent Order.
Attach has no effect on theEntityManager‟s QueryCache.
AddEntity behaves the same way as AttachEntity except as follows:
After 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}) ;
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)
C# ((ICloneable)theEntity).Clone();
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:
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
The one that comes out is the one that went in minus those Territories whose Description property value begins with
the letter “M”.
In the first example, above, our filter applies to the query‟s root type, Territory. We aren‟t limited to that: we can
also apply filters to other types used in the query. Consider the following:
IdeaBlade DevForce Business Object Persistence - Advanced
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:
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.
In the above snippet, we instantiate a new EntityQueryFilterCollection, to which we then add two individual filters,
each of which is a Func<T>. The first filter added imposes a condition on the Customer type; the second imposes a
condition on the OrderSummary type. Note that we could now apply these filters to any query whatsoever. If the
targetted query made use of the Customer type, the condition on Customers would apply; if it made use of the
OrderSummary type, the condition on OrderSummaries would apply. If it made use of both, as does our example q0,
both conditions would apply.
A filter is also applied directly to any clause of a query that returns its targetted type. Thus, the effect of the two
filters defined above, applied against query q0, is to produce a query that would look like the following if written
conventionally:
C# var q0 = _em1.Customers
.Where(c => c.Country.StartsWith("U"))
.Where(c => c.OrderSummaries
.Where(o => o.OrderDate < new DateTime(2009, 1, 1))
.Any(o => o.ShipCity.StartsWith("N")));
IdeaBlade DevForce Business Object Persistence - Advanced
QueryStrategy aQueryStrategy =
new QueryStrategy(FetchStrategy.DataSourceThenCache,
MergeStrategy.PreserveChanges, InversionMode.On);
query.QueryStrategy = aQueryStrategy;
VB
In our initial run, we have the InversionMode set to On. Because DevForce is unable to invert the query, a
QueryInversionServerException is thrown, with the following message:
This query is not automatically invertible and cannot be executed
unless either its QueryInversionMode is set to 'Manual' or its
FetchStrategy is set to Optimized, DataSourceOnly or CacheOnly.
If we change the InversionMode to Try and rerun the query, it runs without an exception, but the Assert test fails,
because no Orders were included in the result set. Why? Because changing the InversionMode from On to Try
didn‟t alter the fact that the query couldn‟t be inverted; it just told DevForce not to worry about that fact. The result
set returned with a FetchStrategy of DataSourceThenCache is only that obtained in a final query against the cache,
after entities retrieved from the data source have been placed there. Since the query was not invertible, no Customer
objects were retrieved into the cache, and that final query returns an empty result.
Suppose now we set the FetchStrategy to DataSourceAndCache. Now references to the Order objects retrieved from
the data source are included in the result set. A second application of the query, this time against the cache, may or
may not pick up additional Orders70. But in any event, the final result set will contain references to the in-cache
Orders that are linked to the specified Customer. This will be true even if, at the end of the process, there are still no
Customer objects in the cache!
70
It will pick up additional Orders if there are Orders in the cache that are (a) linked to Customer “CONSH”, and (b) either do
not exist in the data source, or are not linked to Customer “CONSH” in the data source
IdeaBlade DevForce Business Object Persistence - Advanced
When a query cannot be inverted, a FetchStrategy other than DataSourceThenCache should be used. Table 16
shows the combinations of FetchStrategy and InversionMode that lead to exceptions. Note that these exceptions are
designed to prevent you from receiving query results that, although they may look perfectly valid, are not!
Only queries that either have been inverted or do not require inversion are saved in the query cache.
VB
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:
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.
The distinction between the DataSourceThenCache and DataSourceAndCache strategies is subtle but important in
the case of queries that must process non-targeted types and are therefore subject to query inversion. Suppose you
were to submit the following query:
IdeaBlade DevForce Business Object Persistence - Advanced
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:
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;
The reason that DataSourceThenCache is the preferred FetchStrategy for other circumstances is that, under certain
circumstances, DataSourceAndCache can produce confusing results. Suppose you have some Customer objects in
the cache, including Customer XYZ, and you submit a DataSourceAndCache query for Customers with Orders in
the current year. Customers meeting this condition are fetched from the data source into the cache, and merged there
with Customers already residing in the cache with a MergeStrategy of PreserveChanges. Meanwhile DevForce
hangs on to a list of references to the objects just fetched.
Now it so happens that Customer XYZ, who was in the cache already, had (during the current application session)
just cancelled their one and only order for the current year. The Order was marked for deletion, but this change had
not been committed to the database when the query was submitted. So, based on the state of data in the data source,
Customer XYZ met the query conditions and was retrieved, and a reference to their object in the cache was included
in the set returned by the query against the datasource.
DevForce continued on, resubmitting the query against the cache. This time Customer XYZ did not make the cut
because, according to the data in the cache, they did not have a current year Order. No reference to their in-cache
object was included in the list of pointers resulting from the query against the cache.
IdeaBlade DevForce Business Object Persistence - Advanced
But DataSourceAndCache, DevForce then performed a UNION of the references obtained in the query against the
data source and those obtained in the query against the cache. A reference to cached Customer XYZ therefore
ended up in the result set returned by the query. Your app happily filled a datagrid with the returned Customers, and
there sat Customer XYZ, even though they (quite visibly) did not have an order in the current year! Can a phone call
from your end user be far away?
The DataSourceThenCache FetchStrategy, by contrast, would have retrieved, from the data source and into the
cache, whatever data met the query conditions. It would then have submitted the query against the cache, and only
the Customers meeting the specified condition in that final query would have been included in the returned result
set. Customer XYZ, having been found to have no current year Order, would have been excluded, properly.
Transactional Queries
DevForce query requests are atomic: the developer can issue only one (synchronous) query request at a time. But
when the request resolves into multiple SQL queries, they can all be performed together within the same transaction.
Individual query requests resolve into several SQL queries when the query has includes that fetch related objects or
when the query includes one or more sub-queries and “query inversion” is turned on.
When the root query is performed transactionally, both the main select and the selection of related entities occur
within transactional boundaries.
DevForce developers can set the transaction isolation level on individual commands
Developers can set the transaction isolation level for individual queries and saves.
Implementation
There is a TransactionSettings class and a TransactionSettings property on both the SaveOptions and
QueryStrategy classes.
app.config for an Entity Data Model‟s database, the Object Mapper simply copies the connection information found
in the EDM‟s app.config.
Listing 4 shows the XML Schema element from an Entity Data Model (.edmx) file. Typically, this element is
generated initially by the EDM Designer, then modified slightly the DevForce Object Mapper. Note namespace and
two attributes prefixed with “ib”. These were written into the .edmx file by the DevForce Object Mapper. They are
respected by the EDM Designer, however, and it will not overwrite them even if you use it to generate fresh EDM
code later.
Listing 4. DataSourceKey attribute in the Entity Data Model (.edmx) file
<Schema Namespace="ServerModelNorthwindIB" Alias="Self"
XML xmlns="http://schemas.microsoft.com/ado/2006/04/edm"
xmlns:ib="http//www.ideablade.com/schemas/edmx"
...
ib:DataSourceKey="Default" ib:LastModTs="7/3/2008 12:54:54 PM">
When you save your work in the DevForce Object Mapper , it writes (subject to your okay) a similar connection
string into the app.config file that it saves in the DomainModel project. It writes this information as part of an
edmKey (for relational database sources) or a wsKey (for web service sources). Listing 5 shows the XML
statement written by the Devforce Object Mapper into its app.config for the same object model just referenced:
Listing 5. Data source identifier (edmKey) for run-time operations (written to the app.config file)
Observe the connection information for the Entity Data Model and its datasource, and the DataSourceKeyName,
stored as the name attribute of the edmKey.
DataSourceKeys originally written by the Object Mapper into the app.config file, such as the one just shown, may
subsequently be altered or removed manually by the developer. Other DataSourceKeys may also be added
manually.
Why might a developer alter or add a DataSourceKey in app.config? The most common reason would be that he
wants to add keys that point to multiple variations of a particular Datasource (e.g., Development, Test, Production).
IdeaBlade DevForce Business Object Persistence - Advanced
71
You can find this on an entity instance as its EntityAspect.EntityMetadata.DataSourceKeyName property
IdeaBlade DevForce Business Object Persistence - Advanced
The extension determines which version – e.g., Development, Test, Stage, or Production – of the data source(s)
actually gets accessed by the EntityManager. Expressed another way: the “Extension” identifies a collection of
one or more data sources, each the repository of a set of tables or web services that map to business object
72
classes, which will be accessed by a given EntityManager.
In the illustration below, all four of the DS#1 data sources would have the same DataSourceKeyName. The same
could be said for the DS#2 and DS#3 data sources. On the other hand, the set of data sources accessed by a single
EntityManager would comprise a DS#1, a DS#2, and a DS#3. But which copy of DS#1, a DS#2, and DS#3 should
be used? That would be determined by the DataSourceExtension with which the EntityManager was associated at
instantiation.
Now let‟s look at DataSourceKey names and extensions as they appear in edmKeys and wsKeys in an App.config
file. Listing 6 is an excerpt from an app.config file containing multiple DataSourceKeys with different key names
and extensions. For clarity, we‟ve made sure the name attribute is the first attribute listed for the the <edmKey>
element.
Note that each DataSourceKey, in addition to containing a connection string, also includes probe assembly names
for assemblies that hold auxiliary classes for id generation, authentication, event handling, and the like. 73
Listing 6. Extract of app.config file with multiple DataSourceKeys
XML <edmKeys>
<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="Data
Source=ProductionDBMS_A;Initial Catalog=NorthwindIB;Integrated
Security=True;MultipleActiveResultSets=True""
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="Data Source=ProductionDBMS_B;Initial
72
The default extension, incidentally, is no extension at all. If you create a new DevForce DomainModel and let the Object
Mapper write the edmKey entry into the configuration file, the DataSourceKey will be entered with a name of “Default”,
without an extension.
73
It may also contain a <tag>, where you can put any sort of string-value custom information you desire. At runtime you can
access the information placed there via the Tag property of a DataSourceKey object -- which you can get from the
DataSourceKeys collection of a DataSourceKeyResolver object.
IdeaBlade DevForce Business Object Persistence - Advanced
Catalog=AdventureWorks2000;Integrated Security=True;MultipleActiveResultSets=True""
containerName="ServerModelAw2000.ServerModelAw2000Context"
logTraceString="false" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelAw2000" />
</probeAssemblyNames>
</edmKey>
<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="Data
Source=DevelopmentDBMS_A;Initial Catalog=NorthwindIB;Integrated
Security=True;MultipleActiveResultSets=True""
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="Data Source= DevelopmentDBMS_B;Initial
Catalog=AdventureWorks2000;Integrated Security=True;MultipleActiveResultSets=True""
containerName="ServerModelAw2000.ServerModelAw2000Context"
logTraceString="false" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelAw2000" />
</probeAssemblyNames>
</edmKey>
</edmKeys>
<wsKeys>
<wsKey url="http://api.google.com/search/beta2" endpointName="GoogleSearchPort"
name="GoogleSearch" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
</probeAssemblyNames>
</wsKey>
</wsKeys>
In the above excerpt from an app.config file, edmKeys are present for two databases (NorthwindIB and
Adventureworks2000). Two versions (Development and Release) are maintained of these databases. A wsKey is
present for a Google web service: the same service is used for Development and Production.
Instantiating an EntityManager and specifying a DataSourceKey Extension of “Release”...
IdeaBlade DevForce Business Object Persistence - Advanced
…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
Tenant Extensions
Extensions are also a good way to segment data sources by client in a “multi-tenant application”. Multi-tenant
applications are typical of Application Service Provider (ASP) scenarios in which each customer‟s data is managed
in isolated data sources.
When the user logs in, the application identifies the user‟s parent customer and knows which set of Datasources is
appropriate for that user. The application can then instantiate an EntityManager that draws upon just those data
sources.
IdeaBlade DevForce Business Object Persistence - Advanced
The “DataSourceExtension” is the ideal representation for a customer-specific data source set as in this depiction
of a three-tenant scenario with customers “A”, “B”, and “C”:
Multi-Part Extensions
DataSourceExtensions may have multiple parts, permitted an even more sophisticated scheme for selected a data
source instance at runtime. Consider the following edmKeys in an app.config file (connection value and probe
assembly section removed for brevity):
XML <edmKeys>
</edmKeys>
Note that the first two edmKey names contain two underscores. These delimit multi-part DataSourceExtensions.
Were you to instantiate an EntityManager as follows:
…you would get the database identified in the first edmKey in the above excerpt. On the other hand, if you wrote
this statement:
…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”:
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:
The DataSourceKeyName property of the entity reveals this name; for example:
anEmployee.EntityAspect.EntityMetadata.DataSourceKeyName.
DevForce connects an actual data source at runtime by asking a DataSourceKeyResolver for the DataSourceKey
that corresponds to the key name.
IdeaBlade DevForce Business Object Persistence - Advanced
To be more precise, it returns an object that implements IDataSourceKey. There are three
implementations of this interface at the moment, the EdmKey, the WsKey and the ClientEdmKey.
EdmKey and WsKey provide access and management information for relational database and web service
data sources, respectively. ClientEdmKey has no dependency on the Entity Framework on its data sources.
A DataSourceKeyResolver has a single method, GetKey(KeyName, KeyExtension) to do get this key. The
KeyName is the symbolic data source name that we see inscribed in the entity‟s DataSourceKeyName property.
The KeyExtension, as we have seen, is an optional string for differentiating among multiple keys each referring to
a distinct runtime data source.
DevForce uses its own DefaultDataSourceKeyResolver unless we provide an alternative. The default version
looks for a key in the IdeaBlade section of the application configuration file, App.config; it knows how to find the
requested key definition in the configuration file‟s XML and turn it into the appropriate kind of DataSourceKey.
The App.config is a fixed file that must reside in a known place. That means the key information must be
comparatively static as well.
True, the configuration file does not have to be compiled into the application74. DevForce will prefer a loose version
of the file in the executable‟s directory. We make our change, drop it into the executable‟s directory, and DevForce
will prefer that version over any other. No re-compilation or major re-deployment required.
We may need more flexibility or security than the configuration file affords in situations such as the following:
The connection facts change periodically and we can‟t count on redeploying the updated configuration
file.
The connection facts are different for different users of the application.
The connection facts must not reside in a text file; they must be delivered to the application at runtime
after authenticating the user.
You are having trouble deploying a loose configuration file on IIS.
Custom DataSourceKeyResolver
Fortunately, it is easy to write a custom DataSourceKeyResolver that does exactly what you want it to do.
Pick a project to hold your key resolver, e.g. DomainModel
If in an assembly not already being probed by DevForce, add a top-level probe assembly tag to App.config so
DevForce can find it.
XML <edmKey
connection="metadata=res://ServerModelNorthwindIB/ServerModelNorthwindI
B.csdl|res://ServerModelNorthwindIB/ServerModelNorthwindIB.ssdl|res://S
erverModelNorthwindIB/ServerModelNorthwindIB.msl;provider=System.Data.S
qlClient;provider connection string="Data Source=.;Initial
Catalog=NorthwindIB;Integrated
Security=True;MultipleActiveResultSets=True""
containerName="ServerModelNorthwindIB.ServerModelNorthwindIBContext"
logTraceString="false" name="Default" tag="">
<probeAssemblyNames>
<probeAssemblyName name="DomainModel" />
<probeAssemblyName name="ServerModelNorthwindIB" />
</probeAssemblyNames>
</edmKey>
74
It is compiled into the application by default as an embedded resource of the AppHelper.dll.
IdeaBlade DevForce Business Object Persistence - Advanced
C# using IdeaBlade.EntityModel;
using IdeaBlade.Core;
namespace AppHelper {
[Serializable]
class MyDataSourceKeyResolver : IDataSourceKeyResolver {
public IDataSourceKey GetKey(string keyName, string keyExtension, bool onServer) {
if (!onServer) {return null;}
Console.WriteLine("Shot ya with my resolver"); // Demo code.
// Build your own ClientEdmKey starting with the following
// return new MakeClientEdmKey(keyName, theConnectionString)
return null; // Didn't build key; DefaultDataSourceKeyResolver takes over
}
}
}
VB
Imports IdeaBlade.EntityModel
Imports IdeaBlade.Core
<Serializable()> _
Public Class MyDataSourceKeyResolver : Implements IDataSourceKeyResolver
Public Function GetKey(ByVal keyName As String, _
ByVal keyExtension As String, _
ByVal onServer As Boolean ) _
As IDataSourceKey Implements IDataSourceKeyResolver.GetKey
If !onServer Then Return null
Console.WriteLine("Shot ya with my resolver") ' Demo code.
' Build your own ClientEdmKey starting with the following
' Return New MakeClientEdmKey(keyName, theConnectionString)
Return Nothing ' Didn't build key; DefaultDataSourceKeyResolver takes over
End Function
End Class
THE NET RESULT OF KEY LOOKUP MUST DELIVER A KEY ON BOTH CLIENT AND SERVER. However,
in n-tier, the client should not provide connection info in the key. Note that GetKey receives a boolean onServer
parameter that indicates whether GetKey() is operating on the server or client.
IdeaBlade DevForce Business Object Persistence - Advanced
75
“default”, not coincidentally, is the DevForce Object Mapper‟s default data source key name for the first data source.
76
Entities in the PM may map to more than one data source. The PM will suffix each data source key name with the same
extension.
IdeaBlade DevForce Business Object Persistence - Advanced
Fetched entity
Created entity
Modified entity
Entity undo
Deleted entity
Removed entity
Re-attached entity
Entity merged into the PM from another PM or EntitySet
If the user cancels a Wizard step, we call RollBackCheckpoint, and the EntityManager restores its entity cache
to the state when we began the checkpoint.
We can maintain a stack of checkpoints. We call BeginCheckpoint for step one; the user makes some changes
and proceeds to step 2 where we call BeginCheckpoint again. The EntityManager records a boundary in the
checkpoint log and increases the checkpoint “depth” by one. Now we can rollback either to the first or the second
checkpoint, depending upon how much activity we want to discard.
On the other hand, if the user presses “Ok” and completes the Wizard successfully, we can save all modified entities
and prevent rollbacks prior to the save point by calling EntityManager.SaveChanges().
We don‟t have to save the changes to close a checkpoint “session.” If we call ClearCheckPoints(), the
EntityManager discards the checkpoint log and stops logging entity cache activity. The user‟s changes are still
pending in cache – they are not in the database - but we have erased our checkpoints and can no longer rollback.
We might think of a DevForce WinClient checkpoint as an in-memory transaction along the lines of the more
familiar database transaction. The BeginCheckpoint, RollbackCheckpoint, and SaveChanges correspond
approximately to “Begin Transaction”, “Rollback Transaction”, and “Commit Transaction.” We can nest
checkpoints just as we nest database transactions. We can rollback to any pending checkpoint as we can rollback to
any pending transaction depth.
Why is there a ClearCheckPoints() method but no Commit() method? There is no “Commit” because we
feared a potentially fatal confusion. “Commit,” for most of us, implies a degree of permanence that an in-memory
transaction cannot match. We expect a durable modification of the database after a database “commit”. In contrast,
entity changes are still pending and tenuous after ClearCheckPoints(); they will be lost if the application
terminates before we persist them explicitly with SaveChanges().
We might regard a “checkpoint” as a kind of “snapshot”. A checkpoint differs from the everyday meaning of
“snapshot” in one key respect: a “checkpoint” records changes to the entity cache; a snapshot would record the
entire cache.
Checkpoints are comparatively lightweight. The snapshot of a large entity cache could hold thousands or millions of
entities while the equivalent checkpoint held only a few. A snapshot might be too large to hold in memory; a
checkpoint is compact and easily held in memory.
Checkpoints are efficient, especially for the SaveChanges and ClearCheckPoints operations; the
EntityManager just throws away the log. Rollback is a bit more expensive because the EntityManager must
reverse the logged changes; in most cases rollbacks are rare and the changes are few.
The new EntityManager checkpoint signatures are:
IdeaBlade DevForce Business Object Persistence - Advanced
Method Description
BeginCheckpoint() Start “checkpointing” (first call) or add a new checkpoint level
(subsequent calls). Returns the new checkpoint depth.
RollbackCheckpoint() Rollback one checkpoint. Restores the entity cache to its state one
checkpoint ago. The method returns the new checkpoint depth.
RollbackCheckpoint(int pCount) Rollback “pCount” number of checkpoints ago; returns the new
depth.
RollbackCheckpoints() Rollback all checkpoints and stops checkpointing. Restores the
entity cache to the state prior to the first checkpoint.
ClearCheckpoints() Stop checkpointing and discard the checkpoint log. The entity
cache remains in its current state. Roughly equivalent to a
“commit”.77
IsCheckpointing True if the EntityManager is maintaining checkpoints.
GetCheckpointDepth() Integer of the current checkpoint depth. The first
BeginCheckpoint is depth one; each subsequent call increases the
depth by one and each RollbackCheckpoint() reduces it by one.
The depth is zero when checkpointing stops.
We can create new instances and there are scenarios for which this is useful.
Perhaps we have a long-running query or series of queries that should run in a background thread without blocking
the UI. Maybe we want to poll for changes to a set of entities or be on the look-out for certain conditions in the
database.
Our implementation should use a different EntityManager in the background thread so as not to conflict with the
main manager in the UI thread. When the background process completes, the call-back method can pause the UI,
import data from the second manager, alert the user, and resume the UI.
77
Note that clearing a single checkpoint, or any number less than all of them, is not supported. Changes subsequent to a given
checkpoint may depend upon changes made after earlier checkpoints. It is therefore not possible to support the “commit” of
changes made since a more recent checkpoint while still permitting rollbacks to earlier checkpoints. If you clear checkpoints,
you must clear them all.
IdeaBlade DevForce Business Object Persistence - Advanced
Miscellaneous observations
Different EntityManagers do not interact. It is possible – and useful – to import entities from one PM to the other.
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.
Furthermore, for a class to be thread-safe, it must continue to behave correctly, in the sense described above, when
accessed from multiple threads, regardless of the scheduling or interleaving of the execution of those threads by the
IdeaBlade DevForce Business Object Persistence - Advanced
runtime environment, without any additional synchronization on the part of the calling code. The effect is that
operations on a thread-safe object will appear to all threads to occur in a fixed, globally consistent order.
The relationship between correctness and thread safety is very similar to the relationship between consistency and
isolation used when describing ACID (atomicity, consistency, isolation, and durability) transactions: from the
perspective of a given thread, it appears that operations on the object performed by different threads execute
78
sequentially (albeit in a nondeterministic order) rather than in parallel.
The DevForce EntityManager is safe for multithreaded read operations. If you attempt writes to a single
EntityManager from multiple threads, you must synchronize the write operations yourself. For us to make the
EntityManager thread-safe for write operations would require that we make thread-safe every method therein – and
every method of the business objects it manages, including property setters. This would increase the
EntityManager‟s complexity – and degrade its performance – significantly. Every user of the EntityManager, and
every use thereof, would incur the performance penalty, whether such users and uses required thread-safety or not.
At least 90% of the use cases that people submit to us for multi-threading involve retrieving data while other
operations proceed. For this we have provided Asynchronous Queries. You call the EntityManager‟s
GetEntitiesAsync() method, and we take care of putting the data retrieval operation on a separate thread so that the
rest of your application can continue processing. Any number of such asynchronous queries can be launched
simultaneously. You can read about asynchronous queries in the DevForce Developers Guide (in the chapter on
Object Persistence), and see sample code in the Asynchronous Queries instructional unit that is shipped with the
product.
Does this mean that you can‟t do multi-threading (other than by using Asynchronous Queries) in a DevForce
application? No, it does not. It just means that you should never share a single EntityManager, or any of the
entities it manages in its cache, across multiple threads. Let us repeat:
78
Excerpted from ”Characterizing Thread Safety” by Brian Goetz, available on the web at:
http://www-128.ibm.com/developerworks/java/library/j-jtp09263.html
IdeaBlade DevForce Business Object Persistence - Advanced
Never communicate entities across thread boundaries. If the caller must know about some entities, send a
list of PrimaryKeys across the thread boundary in a call back. Alternatively, you could bury EntitySets in a
call back to serialize copies of entities across the thread boundary.
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.
AsyncSerialTask.Create("ASimpleTask")
.AddAsyncLogin(mgr, new LoginCredential("demo", "demo", "earth"))
.AddAsyncQuery(loginArgs => mgr.Customers.Where(c => c.Country == "USA"))
.AddAction(fetchArgs => {
var customers = fetchArgs.Result;
customers.ForEach(c => c.Country = "US");
})
.AddAsyncSave(mgr)
.Execute(null, (completionArgs) => {
SaveResult sr = completionArgs.Result.Result;
Debug.Assert(sr.Ok);
});
}
AsyncParallelTask
The AsyncParallelTask allows you to create a set of asynchronous actions, execute them in parallel, and provide a
single callback to handle all processing results.
To use the feature, you first create a task, and then add asynchronous actions to it until you have a set ready for
launch via the Execute method.
In the absence of the AsyncParallelTask you would need to issue multiple asynchronous method calls and provide
handlers for each. Instead, the AsyncParallelTask takes care of much of this housekeeping for you. It allows you to
pass an argument to each action in the task, and to specify a single handler when the entire task completes. You can
also specify an ExceptionHandler to provide a single point of error handling.
Each action is executed on a separate worker thread. The completion action is called on the main thread once all
actions have completed. If you've specified a callback for an asychronous action, that callback will also be called on
the main thread. The Execute call returns immediately after starting all of the specified parallel actions.
Use the AsyncSerialTask rather than the AsyncParallelTask if you need to link the outputs from one action to the
inputs to the next, or to mix asynchronous and synchronous actions.
IdeaBlade DevForce Business Object Persistence - Advanced
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);
79
Web service entities and Web service wrappers for the business object model are in the alpha bits at this writing. Please
contact IdeaBlade for more recent information about these important features.
IdeaBlade DevForce Business Object Persistence - Advanced
SOA design emphasizes loose coupling and a contract between a client and a service. The client should know as
little as possible about the service internals and engage with the service only through a message-like interface having
a simple protocol. The interface should be course grained, meaning that we expect to get a lot done with each
service method call and we don‟t over-task the interface with fine details. We are often aware of the boundary
between the client and service.
The services of an SOA application do not belong to that application. In principle, the services are designed
independently of any particular SOA application and could be accessed by any authorized client.
An n-tier application, by contrast, has logical layers that are tightly coupled. The layers tend to have many, fine
grained interface points. The layers are designed to work together as a single, operating whole. It is a secondary
benefit if a tier can serve another application through the same interface.
SOA proponents emphasize the importance of the contract between client and server. But SOA can only ensure the
consistency of the interface points. It can‟t ensure that the semantics implied in the interface are actually the same on
both sides of the fence.
A program manager on Microsoft‟s CLR team made an analogous point in a commentary on the difficulty of
choosing between defining classes and interfaces:
I often hear people saying that interfaces specify contracts. I believe this is a dangerous myth. Interfaces, by
themselves, do not specify much beyond the syntax required to use an object. The interface-as-contract myth causes
people to do the wrong thing when trying to separate contracts from implementation, which is a great engineering
practice. Interfaces separate syntax from implementation, which is not that useful, and the myth provides a false
sense of doing the right engineering. In reality, the contract is semantics, and these can actually be nicely expressed
with some implementation.[emphases ours]
Krzysztof Cwalina, [Framework Design, 80]
N-tier applications can impose much stricter contracts than SOA applications. They can enforce common semantics
by requiring both sides to implement the contract by using the same object classes. A DevForce application forces
the type on the server tier to be exactly the same as the type on the client tier. This is known as “type fidelity”.
Hiding implementation details is as essential to n-tier design as it is to SOA design; each layer should know as little
as possible about the design and works of the other layers. But an n-tier application can and should impose cross-tier
requirements if these help realize application objectives. For example, we can require that the data access tier
communicate with a UI tier via .NET remoting rather than Web services if this makes the application less complex
and perform better; such objectives may matter far more to the customer right now than exposing the business object
model as a service80.
An n-tier application can also be an application with a Service Oriented Architecture. The application may
implement some number of features by invoking a Web service or by embedding a Web service within an object
wrapper. The tier may expose some of the application‟s own functionality as Web services. In such cases, the
application is communicating externally via the service.
Cross-tier interactions, on the other hand, are communications within the application.
Let not blind obedience to SO orthodoxy triumph over rational choice.
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.
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.
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;
}
}
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
#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# [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:
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:
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.
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.
C# using System;
using System.Collections.Generic;
using System.Linq;
//using System.Web;
using System.Diagnostics;
using System.Text;
using System.Linq.Expressions;
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 {
#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
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.
IdeaBlade DevForce Business Object Persistence - Advanced
…[snip]
#endregion
To wrap an Entity at runtime for the purpose of getting at the EntityAspect property, call the static method
EntityWrapper.Wrap():
Having done that, you can then ask questions like the following…
…and of course, use any of the other facilities of EntityAspect. (Those are documented in the Developers Guide
chapter “Class Libraries”.)
[Key]
public string Abbrev {
get {
return _abbrev;
}
set {
_abbrev = 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:
Using IdeaBlade.EntityModel;
C#
[DiscoverableType(DiscoverableTypeMode.KnownType)]
public class State {
public State() {
}
}
}
VB
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.
IdeaBlade DevForce Business Object Persistence - Advanced
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
C#
[DataContract]
[KnownType(typeof(List<City>)]
public class State {
public State() {}
[... snip]
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:
IdeaBlade DevForce Business Object Persistence - Advanced
C#
public class PocoSaveAdapter : EntitySaveAdapter {
public PocoSaveAdapter() {
}
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
82
DevForce recognizes either attribute, as a convenience for those porting their app from RIA services
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.
[DiscoverableType(DiscoverableTypeMode.ServerService)]
C# // or [EnableClientAccess]
public class PocoSaveAdapterUsingConventions {
public PocoSaveAdapterUsingConventions() {
}
“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).
330 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
331 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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.
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.
332 | P a g e
IdeaBlade DevForce Validation Through Verification
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:
333 | P a g e
IdeaBlade DevForce Validation Through Verification
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
334 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
335 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
336 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
337 | P a g e
IdeaBlade DevForce Validation Through Verification
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!
338 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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.
339 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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.
340 | P a g e
IdeaBlade DevForce Validation Through Verification
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 A Verifier validates the state of an object.
VerifierCollection This is the abstract base class for a family of verifiers described below.
VerifierResult Verifier execution produces a VerifierResult object. This object, in
VerifierResultCollection 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
A Verifier validates the state of an object. A Verifier can only run after it is instantiated by a VerifierEngine.
341 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
342 | P a g e
IdeaBlade DevForce Validation Through Verification
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 A harness for a custom property verifier that validates an object of
(Of T)
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.
343 | P a g e
IdeaBlade DevForce Validation Through Verification
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
344 | P a g e
IdeaBlade DevForce Validation Through Verification
84
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.
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.
345 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
346 | P a g e
IdeaBlade DevForce Validation Through Verification
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”.
347 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
348 | P a g e
IdeaBlade DevForce Validation Through Verification
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. Delegate method that takes a type and a string (presumed to be the
PropertyNameTranslator 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. Delegate method called by the VerifierEngine after every verifier
VerifierBatchInterceptor 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.
349 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
350 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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.
351 | P a g e
IdeaBlade DevForce Validation Through Verification
The following are types closely related to this class and its derived classes.
Type Description
DelegatePropertyValueVerifier The foundation of a custom property verifier. The developer implements
(Of T)
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.
352 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
353 | P a g e
IdeaBlade DevForce Validation Through Verification
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!
354 | P a g e
IdeaBlade DevForce Validation Through Verification
Verifiers
We use an instance of the DevForce abstract Verifier class to implement a 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 instance 87. 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
89
Potato.Name .
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.
355 | P a g e
IdeaBlade DevForce Validation Through Verification
C#
public virtual VerifierApplicability IsApplicable(
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
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.
356 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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.
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.
357 | P a g e
IdeaBlade DevForce Validation Through Verification
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
Custom Verifiers
91
They can even manipulate the VerifierResultCollection itself; one hopes they are prudent in doing so.
358 | P a g e
IdeaBlade DevForce Validation Through Verification
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 user 92 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.
VB
92
Or perhaps to a log file if we are validating outside of a user interface.
359 | P a g e
IdeaBlade DevForce Validation Through Verification
C#
public DelegateVerifier(String pDescription,
VerifierCondition<T> pVerifierCondition)
Visual Basic
' DelegateVerifier Constructor
Public Sub New (ByVal pDescription As String, _
ByVal pVerifierCondition As VerifierCondition(Of T))
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
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.
360 | P a g e
IdeaBlade DevForce Validation Through Verification
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 language 94. 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.
94
The .NET practices for localization are beyond the scope of this document.
361 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
362 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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 so 99.
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.
363 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
C#
public VerifierApplicability IsApplicable(
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
Notice that the second parameter is a TriggerContext. The TriggerContext provides the verifier with vital
information about how the verifier was triggered.
364 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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”.
365 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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")
C#
TriggerLink aLink = new TriggerLink(
366 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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.
367 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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);
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).
368 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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))
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.
369 | P a g e
IdeaBlade DevForce Validation Through Verification
The DevForce Object Mapper generates property setter code that calls a VerifierEngine in a trigger-like way.
You do the same when you write your own custom settable properties.
You could put the same call logic inside a method. For example, you might trigger Order verification inside
methods that add or remove OrderDetail items so that you can immediately test the effect of adds and deletes on
the total price of an order.
TriggerTiming (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.
Verifier Discovery
The VerifierEngine always discovers verifiers in the types it is asked to verify 108. 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.
370 | P a g e
IdeaBlade DevForce Validation Through Verification
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
verifiers.Add(GetHireDateRangeVerifier());
verifiers.Add(new BirthDateRangeVerifier());
verifiers.Add(GetBornBeforeHiredVerifier());
verifiers.Add(GetPhoneNumberVerifier(Employee.HomePhoneEntityProperty));
return verifiers;
}
#endregion
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.
371 | P a g e
IdeaBlade DevForce Validation Through Verification
#endregion
372 | P a g e
IdeaBlade DevForce Validation Through Verification
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) {
#endregion
373 | P a g e
IdeaBlade DevForce Validation Through Verification
#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 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.
374 | P a g e
IdeaBlade DevForce Validation Through Verification
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 on 111. 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!
111
We saw how to do this in the “Getting Started” section.
375 | P a g e
IdeaBlade DevForce Validation Through Verification
Instance Verification
The following are the VerifierEngine.Execute overloads for instance verification:
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.
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).
376 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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.
377 | P a g e
IdeaBlade DevForce Validation Through Verification
The following are the VerifierEngine.Execute overloads for “preset” trigger verification:
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.
378 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
379 | P a g e
IdeaBlade DevForce Validation Through Verification
The following are the VerifierEngine.Execute overloads for “postset” trigger verification:
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
380 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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.
381 | P a g e
IdeaBlade DevForce Validation Through Verification
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
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.
382 | P a g e
IdeaBlade DevForce Validation Through Verification
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.
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.
119
Inherit is the default value for all new UserControls . 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.
383 | P a g e
IdeaBlade DevForce Validation Through Verification
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:
C#
this.AutoValidate =
System.Windows.Forms.AutoValidate.EnableAllowFocusChange; // Can move
Visual Basic
me.AutoValidate = _
System.Windows.Forms.AutoValidate.EnableAllowFocusChange ' Can move
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.
384 | P a g e
IdeaBlade DevForce Validation Through Verification
This Employee will not survive validation, will not be saved, and the user will be told why.
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.
385 | P a g e
IdeaBlade DevForce DevForce Silverlight Apps
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.
386 | P a g e
IdeaBlade DevForce DevForce Silverlight Apps
387 | P a g e
IdeaBlade DevForce DevForce Silverlight Apps
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.
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”.
388 | P a g e
IdeaBlade DevForce DevForce Silverlight Apps
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:
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
389 | P a g e
IdeaBlade DevForce DevForce Silverlight Apps
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:
390 | P a g e
IdeaBlade DevForce DevForce Silverlight Apps
<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:
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)
391 | P a g e
IdeaBlade DevForce DevForce Silverlight Apps
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.
392 | P a g e
IdeaBlade DevForce 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.
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.
393 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
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.
394 | P a g e
IdeaBlade DevForce WinForm User Interfaces
[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.
395 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
396 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
397 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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 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.
398 | P a g e
IdeaBlade DevForce WinForm User Interfaces
//
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.
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.
399 | P a g e
IdeaBlade DevForce WinForm User Interfaces
DevForce EF
WinForm Databinding Architecture
High-Level View
Formatting
Data Source
Data Target
(Business DataConverter DataBinder
(UI Control)
Object)
Validation Parsing
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.
400 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
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.
401 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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).
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.
402 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
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.
403 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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");
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.
404 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
405 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
406 | P a g e
IdeaBlade DevForce WinForm User Interfaces
BindingDescriptors Revisited
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.
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.
407 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
408 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
409 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
410 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
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
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.
411 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
Clicking the buttons moves us an object at a time within the Employees of the DataSource.
412 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
Formatting
Formatting is the process by which data is transformed on its way from a datasource to a UI control.
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.
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)
413 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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)
mUnitPriceTextBox.DataBindings.Add(aBinding);
}
mUnitPriceTextBox.DataBindings.Add(aBinding)
End Sub
132
DateConverter, DateRangeConverter, NumericConverter, and NumericRangeConverter
414 | P a g e
IdeaBlade DevForce WinForm User Interfaces
d Short date Displays a pattern defined by the DateTimeFormatInfo.ShortDatePattern property associated with the current
pattern thread or by a specified format provider.
D Long date Displays a pattern defined by the DateTimeFormatInfo.LongDatePattern property associated with the current
pattern thread or by a specified format provider.
t Short time Displays a pattern defined by the DateTimeFormatInfo.ShortTimePattern property associated with the current
pattern thread or by a specified format provider.
T Long time Displays a pattern defined by the DateTimeFormatInfo.LongTimePattern property associated with the current
pattern thread or by a specified format provider.
f Full date/time Displays a combination of the long date and short time patterns, separated by a space.
pattern (short
time)
F Full date/time Displays a pattern defined by the DateTimeFormatInfo.FullDateTimePattern property associated with the current
pattern (long thread or by a specified format provider.
time)
g General Displays a combination of the short date and short time patterns, separated by a space.
date/time
pattern (short
time)
G General Displays a combination of the short date and long time patterns, separated by a space.
date/time
pattern (long
time)
M or m Month day Displays a pattern defined by the DateTimeFormatInfo.MonthDayPattern property associated with the current
pattern thread or by a specified format provider.
R or r RFC1123 Displays a pattern defined by the DateTimeFormatInfo.RFC1123Pattern property associated with the current
pattern 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 Displays a pattern defined by the DateTimeFormatInfo.SortableDateTimePattern property associated with the
date/time current thread or by a specified format provider. The property references the CultureInfo.InvariantCulture
pattern; property, and the format follows the custom pattern "yyyy-MM-ddTHH:mm:ss".
conforms to
ISO 8601
U Universal Displays a pattern defined by the DateTimeFormatInfo.FullDateTimePattern property associated with the current
sortable thread or by a specified format provider. The time displayed is the universal time, rather than the local time,
date/time equivalent to the DateTime value.
pattern
Y or y Year month Displays a pattern defined by the DateTimeFormatInfo.YearMonthPattern property associated with the current
pattern thread or by a specified format provider.
Table 18. Formatting Strings for Numeric Types (from the .NET Framework Help File)
Format Name Description
415 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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 The number is converted to a string of the form "-d.ddd…E+ddd" or "-d.ddd…e+ddd", where each 'd' indicates a
(exponential) 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.
416 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
mUnitPriceTextBox.DataBindings.Add(aBinding);
}
mUnitPriceTextBox.DataBindings.Add(aBinding)
End Sub
417 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
418 | P a g e
IdeaBlade DevForce WinForm User Interfaces
419 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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 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)))
420 | P a g e
IdeaBlade DevForce WinForm User Interfaces
and display these properties in controls on the form itself for the user to see and, perhaps, change.
421 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
422 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
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.
423 | P a g e
IdeaBlade DevForce WinForm User Interfaces
424 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
425 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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
426 | P a g e
IdeaBlade DevForce WinForm User Interfaces
contained Order objects hold strong references to the intermediate object; but the intermediate object has only a
weak reference to the containing list.
Contained
List
Object
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);
VB
Public Sub BindableListMemoryLeak()
Dim aList As IList(Of Order) = FillOrderList(New BindableList(Of Order)(), True)
CloneList(aList)
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.
427 | P a g e
IdeaBlade DevForce WinForm User Interfaces
Parameters:
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:
428 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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) {
429 | P a g e
IdeaBlade DevForce WinForm User Interfaces
VB
Public Class EmployeeLastNameFirstNameAscendingComparer
Implements IComparer(Of Employee)
430 | P a g e
IdeaBlade DevForce WinForm User Interfaces
We could use the above IComparer to sort a BindableList of Employee objects in a statement like the following:
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!
431 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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
int retVal = 0;
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)
432 | P a g e
IdeaBlade DevForce WinForm User Interfaces
End Class
C#
using System.ComponentModel;
using IdeaBlade.Persistence;
using IdeaBlade.Util.PropertyDescriptorFns;
433 | P a g e
IdeaBlade DevForce WinForm User Interfaces
VB
Imports System.ComponentModel
Imports IdeaBlade.Persistence
Imports IdeaBlade.Util.PropertyDescriptorFns
End Class
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:
434 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
435 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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)
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!).
436 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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 DarkRed 137; 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.
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.
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.
437 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
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.
438 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
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.
439 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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!
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.
440 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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”
441 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
“__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.
442 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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!
443 | P a g e
IdeaBlade DevForce WinForm User Interfaces
444 | P a g e
IdeaBlade DevForce WinForm User Interfaces
Select the type whose properties you wish to bind to UI controls, and click OK. You‟ll see a dialog similar to the
following:
445 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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!
446 | P a g e
IdeaBlade DevForce WinForm User Interfaces
447 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
448 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
449 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
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).
450 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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:
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.
451 | P a g e
IdeaBlade DevForce WinForm User Interfaces
452 | P a g e
IdeaBlade DevForce WinForm User Interfaces
453 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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();
454 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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;
455 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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()
456 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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
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
457 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
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.
458 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
459 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
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.
460 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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
461 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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:
...
aDataGridViewBindingManager.BindingSource = _ordersBindingSource;
_ordersBindingSource.DataSource = _orders;
_orders.ReplaceRange(_entityManager.Orders.ToList());
...
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:
Now, as the datagrid is being populated, the Customer associated with each Order can be retrieved at light-speed
from the local cache.
462 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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
463 | P a g e
IdeaBlade DevForce WinForm User Interfaces
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.
464 | P a g e
IdeaBlade DevForce Web Applications
Web Applications
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.
465 | P a g e
IdeaBlade DevForce Web Applications
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)
466 | P a g e
IdeaBlade DevForce Web Applications
After the developer has selected an EntityAdapterManager, the EntityTypeName and EntityAssemblyName
properties are computed.
467 | P a g e
IdeaBlade DevForce Web Applications
468 | P a g e
IdeaBlade DevForce Business Object Server
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:
469 | P a g e
IdeaBlade DevForce Business Object Server
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.
470 | P a g e
IdeaBlade DevForce Business Object Server
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”:
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
471 | P a g e
IdeaBlade DevForce Business Object Server
The components intrinsic to and orbiting the Business Object Server have confusingly similar names. Here’s a brief
review
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:
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.
472 | P a g e
IdeaBlade DevForce Business Object Server
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>
473 | P a g e
IdeaBlade DevForce Business Object Server
String pTypeName,
String pMethodName,
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,
474 | P a g e
IdeaBlade DevForce Business Object Server
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#
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#
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.
475 | P a g e
IdeaBlade DevForce Business Object Server
These “broadcasts” are received by client applications in the method specified when registering the callback.
C#
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.
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
476 | P a g e
IdeaBlade DevForce Business Object Server
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).
EntityService.svc
IIS
<%@ServiceHost language="C#"
Service="IdeaBlade.EntityModel.Server.RemoteEntityService"
477 | P a g e
IdeaBlade DevForce Business Object Server
%>
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"
%>
IIS
<%@ServiceHost language=”C#”
Service="IdeaBlade.EntityModel.Server.EntityServer, Dev"
Factory="IdeaBlade.EntityModel.Server.ServerHostFactory"
%>
478 | P a g e
IdeaBlade DevForce Business Object Server
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.
479 | P a g e
IdeaBlade DevForce Business Object Server
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
480 | P a g e
IdeaBlade DevForce Business Object Server
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”.
481 | P a g e
IdeaBlade DevForce Disconnected Applications
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.
482 | P a g e
IdeaBlade DevForce Disconnected Applications
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):
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?
483 | P a g e
IdeaBlade DevForce Disconnected Applications
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.
Overload Description
484 | P a g e
IdeaBlade DevForce Disconnected Applications
Here is some sample code that uses the first-listed overload of SaveCacheState() to write the cache contents to a disk
file:
485 | P a g e
IdeaBlade DevForce Disconnected Applications
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”.
486 | P a g e
IdeaBlade DevForce Disconnected Applications
Overload Description
487 | P a g e
IdeaBlade DevForce Disconnected Applications
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);
_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.
488 | P a g e
IdeaBlade DevForce Disconnected Applications
The following code uses the first-listed overload of RestoreCacheState() to load the contents of a stored EntitySet to
the local cache:
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”.
489 | P a g e
IdeaBlade DevForce Disconnected Applications
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.
490 | P a g e
IdeaBlade DevForce Disconnected Applications
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.
491 | P a g e
IdeaBlade DevForce Disconnected Applications
492 | P a g e
IdeaBlade DevForce Disconnected Applications
493 | P a g e
IdeaBlade DevForce Disconnected Applications
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.
494 | P a g e
IdeaBlade DevForce Disconnected Applications
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);
}
}
495 | P a g e
IdeaBlade DevForce Disconnected Applications
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.
496 | P a g e
IdeaBlade DevForce Security
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.
Authorization Role-based security restricts access and update to data based on permissions granted to a
logged-in application user.
Authentication
There are two sides to the authentication equation: the client must authenticate the server and the server must
authenticate the client.
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,
497 | P a g e
IdeaBlade DevForce Security
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:
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.
498 | P a g e
IdeaBlade DevForce Security
499 | P a g e
IdeaBlade DevForce Security
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.
500 | P a g e
IdeaBlade DevForce Security
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:
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:
[RequiresAuthentication]
public void OnFetching(EntityServerFetchingEventArgs e) { }
}
501 | P a g e
IdeaBlade DevForce Security
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.
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.
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.
502 | P a g e
IdeaBlade DevForce Security
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>
(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.
<system.web>
XML <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.)
503 | P a g e
IdeaBlade DevForce Security
(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>
504 | P a g e
IdeaBlade DevForce Security
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
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
505 | P a g e
IdeaBlade DevForce Security
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
506 | P a g e
IdeaBlade DevForce Deployment
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.
507 | P a g e
IdeaBlade DevForce Deployment
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.)
508 | P a g e
IdeaBlade DevForce Deployment
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.
509 | P a g e
IdeaBlade DevForce Deployment
Element Description
Probe Assembly The developer may provide custom implementations for any of several kinds of
Names interfaces. Examples include IEntityLoginManager and
147
IDataSourceKeyResolver . 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
148
examine when probing for a class of general applicability .
149
Options “Options” represents a bag of application-wide <option/> tags . 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.
510 | P a g e
IdeaBlade DevForce Deployment
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.
511 | P a g e
IdeaBlade DevForce Deployment
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.
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.
512 | P a g e
IdeaBlade DevForce Deployment
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).
513 | P a g e
IdeaBlade DevForce Deployment
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.
Interface Top-Level or
DataSourceKey-
Specific Client- or Server-
Side Probing
514 | P a g e
IdeaBlade DevForce Deployment
Attribute Top-Level or
DataSourceKey-
Specific Client- or Server-
Side Probing
Attribute
AllowRpcAttribute
DiscoverableMemberAttribute - AllowRSM
Interface Name
ILoginCredential
515 | P a g e
IdeaBlade DevForce Deployment
For example, the assembly display name for the DomainModel.dll assembly might look like the
following:
(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.
516 | P a g e
IdeaBlade DevForce Deployment
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:
to this:
517 | P a g e
IdeaBlade DevForce Deployment
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.
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.
518 | P a g e
IdeaBlade DevForce Deployment
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:
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
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.
519 | P a g e
IdeaBlade DevForce Deployment
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:
520 | P a g e
IdeaBlade DevForce Deployment
remoteBaseURL="http://localhost "
serverPort="9009"
serviceName=" EntityService.svc"
/>
to this:
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.
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.
521 | P a g e
IdeaBlade DevForce Deployment
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:
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
522 | P a g e
IdeaBlade DevForce Deployment
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.
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:
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.
523 | P a g e
IdeaBlade DevForce 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.
524 | P a g e
IdeaBlade DevForce Deployment
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.
525 | P a g e
IdeaBlade DevForce Deployment
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.
526 | P a g e
IdeaBlade DevForce Deployment
Building Blocks
In this section you will find detail on steps referenced in the deployment recipes provided above.
527 | P a g e
IdeaBlade DevForce Deployment
Component Client-Side File Set (all deployment types) Server-Side File Set (deployment to
more than two tiers)
Application MyApp.exe (example) - not needed -
executable (EXE)
IdeaBlade.UI.dll
and related UI IdeaBlade.UI.WinForms.dll
assemblies Optional 3rd party UI control assemblies
DevForce UI - not needed -
IdeaBlade.UI.WinForms.DotNetControls
control assemblies
(one or more) 155 IdeaBlade.UI.WinForms.DevExpressControls
IdeaBlade.UI.WinForms.Infragistics.Controls
DevForce Business IdeaBlade.EntityModel.Server.dll 156
IdeaBlade.EntityModel.Server.dll157
Object Server
(BOS) ServerConsole.exe (Console App)
ServerService.exe (Win Service)
Web.config and Global.asax (IIS)
Application AppHelper.dll 158 AppHelper.dll
business object
<DomainModel name>.dll(example) <DomainModel name>.dll
assemblies
<EntityDataModel name>.dll159 <EntityDataModel name>.dll
160
App.config app.config (Client version) app.config (Server version)
155
The DevForce UI control assemblies you need depend upon which 3 rd 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.
528 | P a g e
IdeaBlade DevForce Deployment
Component Client-Side File Set (all deployment types) Server-Side File Set (deployment to
more than two tiers)
DevForce
IdeaBlade.EntityModel.dll IdeaBlade.EntityModel.dll
persistence library
assemblies IdeaBlade.Core.dll IdeaBlade.Core.dll
IdeaBlade.Validation.dll IdeaBlade.Validation.dll
IdeaBlade.Linq.dll
DevForce WinTraceViewer.exe
WinTraceViewer.exe
TraceViewer
(optional)
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.
529 | P a g e
IdeaBlade DevForce Deployment
IdeaBlade IdeaBlade.EntityModel.Server.dll
assemblies
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.
XML <edmKey
connection="metadata=.\NorthwindIB.csdl|.\NorthwindIB.ssdl|.\NorthwindIB.msl;pr
530 | P a g e
IdeaBlade DevForce Deployment
531 | P a g e
IdeaBlade DevForce Deployment
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.
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.
532 | P a g e
IdeaBlade DevForce Deployment
3. Create a bin sub-directory, and place in it all assemblies (and metadata artifact files) identified above as
necessary.
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).
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.)
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.
533 | P a g e
IdeaBlade DevForce Deployment
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.
' 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.
534 | P a g e
IdeaBlade DevForce Deployment
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.
535 | P a g e
IdeaBlade DevForce Deployment
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.
161
…and AppHelper.dll, should you be using that
536 | P a g e
IdeaBlade DevForce Deployment
11. You can then click the Install button to install MyApp from the published site to your local client
machine.
537 | P a g e
IdeaBlade DevForce Deployment
10. Simply click OK and MyApp will be updated to the new version 1.0.0.1 and running along.
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.
538 | P a g e
IdeaBlade DevForce Deployment
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 3 rd party installer if your requirements get even a little complex. You
must then abandon the Click Once auto-update feature.
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.
539 | P a g e
IdeaBlade DevForce Deployment
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.
540 | P a g e
IdeaBlade DevForce Deployment
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.
Our application starts, and we open a tab for editing, which causes the app to retrieve some data:
541 | P a g e
IdeaBlade DevForce Deployment
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:
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".
542 | P a g e
IdeaBlade DevForce Deployment
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"
543 | P a g e
IdeaBlade DevForce Deployment
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.
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.
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.
544 | P a g e
IdeaBlade DevForce Deployment
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.
Cannot continue. The application is One of the manifest files in the deployment is syntactically not valid, or contains a hash
improperly formatted. Contact the that cannot be reconciled with the corresponding file. This error may also indicate that the
application publisher for assistance. 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.
Application validation did not succeed.
Unable to continue.
Cannot retrieve application. Authentication One or more files in the deployment cannot be downloaded because you do not have
error. 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
Application installation did not succeed. extension that makes the Web server treat it as a protected file. Also, a directory that
Cannot locate applications files on the contains one or more of the application's files might require a username and password in
server. Contact the application publisher or order to access.
your administrator for assistance.
Cannot download the application. The One or more of the files listed in the application manifest cannot be found on the server.
application is missing required files. Verify that you have uploaded all the deployment's dependent files, and try again.
Contact the application vendor or your
system administrator for assistance.
Application download did not succeed. ClickOnce cannot establish a network connection to the server. Examine the server's
Check your network connection, or contact availability and the state of your network.
your system administrator or network
service provider.
An error has occurred writing to the hard This may indicate insufficient disk space for storing the application, but it may also
disk. There might be insufficient space indicate a more general I/O error when you are trying to save the application files to the
available on the disk. Contact the drive.
application vendor or your system
administrator for assistance.
Cannot start the application. There is not The hard disk is full. Clear off space and try to run the application again.
enough available space on the disk.
Too many deployed activations are ClickOnce limits the number of different applications that can start at the same time. This
attempting to load at once. 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.
545 | P a g e
IdeaBlade DevForce Deployment
Shortcuts cannot be activated over the Shortcuts to a ClickOnce application can only be started on the local hard disk. They
network. 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 An application that runs in partial trust cannot be larger than half of the size of the online
partial trust. Contact the application vendor application quota, which by default is 250
or your system administrator for assistance.
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.
546 | P a g e
IdeaBlade DevForce Deployment
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;
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.
547 | P a g e
IdeaBlade DevForce Troubleshooting
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:
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?
548 | P a g e
IdeaBlade DevForce Troubleshooting
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:
549 | P a g e
IdeaBlade DevForce Troubleshooting
}
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:
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:
550 | P a g e
IdeaBlade DevForce Troubleshooting
Contacting Support
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 info@ideablade.com 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.
551 | P a g e
IdeaBlade DevForce Troubleshooting
552 | P a g e
IdeaBlade DevForce Troubleshooting
553 | P a g e